From 3dd079b308574625eaf1c7917ea2636eba46c879 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 19:43:45 +0000 Subject: [PATCH 001/141] LibWeb/HTML: Add stub for WebDriver BiDi to intercept file dialogs Corresponds to https://github.com/whatwg/html/commit/09ad1b989476d32fd674015aa8b1b840a8f220a3 --- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 59 +++++++++++++-------- Libraries/LibWeb/HTML/HTMLSelectElement.cpp | 22 +++++--- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index ca46d705a4f..ac12a592431 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -355,39 +355,52 @@ static void show_the_picker_if_applicable(HTMLInputElement& element) if (!element.supports_a_picker()) return; - // 5. If element's type attribute is in the File Upload state, then run these steps in parallel: + // 5. If element is an input element and element's type attribute is in the File Upload state, then run these steps in parallel: if (element.type_state() == HTMLInputElement::TypeAttributeState::FileUpload) { // NOTE: These steps cannot be fully implemented here, and must be done in the PageClient when the response comes back from the PageHost + // See: ViewImplementation::on_request_file_picker, Page::did_request_file_picker(), Page::file_picker_closed() // 1. Optionally, wait until any prior execution of this algorithm has terminated. - // 2. Display a prompt to the user requesting that the user specify some files. - // If the multiple attribute is not set on element, there must be no more than one file selected; otherwise, any number may be selected. - // Files can be from the filesystem or created on the fly, e.g., a picture taken from a camera connected to the user's device. - // 3. Wait for the user to have made their selection. - // 4. If the user dismissed the prompt without changing their selection, + // FIXME: 2. Let dismissed be the result of WebDriver BiDi file dialog opened with element. + bool dismissed = false; + // 3. If dismissed is false: + if (!dismissed) { + // 1. Display a prompt to the user requesting that the user specify some files. + // If the multiple attribute is not set on element, there must be no more than one file selected; + // otherwise, any number may be selected. + // Files can be from the filesystem or created on the fly, e.g., a picture taken from a camera connected + // to the user's device. + // 2. Wait for the user to have made their selection. + auto accepted_file_types = element.parse_accept_attribute(); + auto allow_multiple_files = element.has_attribute(HTML::AttributeNames::multiple) ? AllowMultipleFiles::Yes : AllowMultipleFiles::No; + auto weak_element = element.make_weak_ptr(); + + element.set_is_open(true); + element.document().browsing_context()->top_level_browsing_context()->page().did_request_file_picker(weak_element, accepted_file_types, allow_multiple_files); + } + // 4. If dismissed is true or if the user dismissed the prompt without changing their selection, // then queue an element task on the user interaction task source given element to fire an event named cancel at element, // with the bubbles attribute initialized to true. + else { + // FIXME: Handle the "dismissed is true" case here. + } // 5. Otherwise, update the file selection for element. - - auto accepted_file_types = element.parse_accept_attribute(); - auto allow_multiple_files = element.has_attribute(HTML::AttributeNames::multiple) ? AllowMultipleFiles::Yes : AllowMultipleFiles::No; - auto weak_element = element.make_weak_ptr(); - - element.set_is_open(true); - element.document().browsing_context()->top_level_browsing_context()->page().did_request_file_picker(weak_element, accepted_file_types, allow_multiple_files); - return; } - // 6. Otherwise, the user agent should show any relevant user interface for selecting a value for element, - // in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.) - // If such a user interface is shown, it must respect the requirements stated in the relevant parts of the specification for how element - // behaves given its type attribute state. (For example, various sections describe restrictions on the resulting value string.) + // 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element, in the + // way it normally would when the user interacts with the control. + // When showing such a user interface, it must respect the requirements stated in the relevant parts of the + // specification for how element behaves given its type attribute state. (For example, various sections describe + // restrictions on the resulting value string.) // This step can have side effects, such as closing other pickers that were previously shown by this algorithm. - // (If this closes a file selection picker, then per the above that will lead to firing either input and change events, or a cancel event.) - if (element.type_state() == HTMLInputElement::TypeAttributeState::Color) { - auto weak_element = element.make_weak_ptr(); - element.set_is_open(true); - element.document().browsing_context()->top_level_browsing_context()->page().did_request_color_picker(weak_element, Color::from_string(element.value()).value_or(Color(0, 0, 0))); + // (If this closes a file selection picker, then per the above that will lead to firing either input and change + // events, or a cancel event.) + else { + if (element.type_state() == HTMLInputElement::TypeAttributeState::Color) { + auto weak_element = element.make_weak_ptr(); + element.set_is_open(true); + element.document().browsing_context()->top_level_browsing_context()->page().did_request_color_picker(weak_element, Color::from_string(element.value()).value_or(Color(0, 0, 0))); + } } } diff --git a/Libraries/LibWeb/HTML/HTMLSelectElement.cpp b/Libraries/LibWeb/HTML/HTMLSelectElement.cpp index 2501450342f..855b3f4f08c 100644 --- a/Libraries/LibWeb/HTML/HTMLSelectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLSelectElement.cpp @@ -436,7 +436,7 @@ static String strip_newlines(Optional string) void HTMLSelectElement::show_the_picker_if_applicable() { // FIXME: Deduplicate with HTMLInputElement - // To show the picker, if applicable for a select element: + // To show the picker, if applicable for a select element element: // 1. If element's relevant global object does not have transient activation, then return. auto& relevant_global = as(relevant_global_object(*this)); @@ -450,15 +450,21 @@ void HTMLSelectElement::show_the_picker_if_applicable() // 3. Consume user activation given element's relevant global object. relevant_global.consume_user_activation(); - // 4. If element's type attribute is in the File Upload state, then run these steps in parallel: - // Not Applicable to select elements + // 4. If element does not support a picker, then return. + // NB: Select elements always support a picker. - // 5. Otherwise, the user agent should show any relevant user interface for selecting a value for element, - // in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.) - // If such a user interface is shown, it must respect the requirements stated in the relevant parts of the specification for how element - // behaves given its type attribute state. (For example, various sections describe restrictions on the resulting value string.) + // 5. If element is an input element and element's type attribute is in the File Upload state, then run these steps + // in parallel: + // NB: Not applicable to select elements. + + // 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element, in the + // way it normally would when the user interacts with the control. + // When showing such a user interface, it must respect the requirements stated in the relevant parts of the + // specification for how element behaves given its type attribute state. (For example, various sections describe + // restrictions on the resulting value string.) // This step can have side effects, such as closing other pickers that were previously shown by this algorithm. - // (If this closes a file selection picker, then per the above that will lead to firing either input and change events, or a cancel event.) + // (If this closes a file selection picker, then per the above that will lead to firing either input and change + // events, or a cancel event.) // Populate select items m_select_items.clear(); From 95730c66aa34f5b159f595b5a374f20250c451f0 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 19:59:06 +0000 Subject: [PATCH 002/141] LibWeb: Make non-replaced flex items make use of transferred size Corresponds to https://github.com/w3c/csswg-drafts/commit/9beaea1f2a23d935a28aef8882bfea5519e1cfde --- .../LibWeb/Layout/FlexFormattingContext.cpp | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 6a1a67bf974..ae8a73cba60 100644 --- a/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -758,25 +758,37 @@ Optional FlexFormattingContext::transferred_size_suggestion(FlexItem CSSPixels FlexFormattingContext::content_based_minimum_size(FlexItem const& item) const { auto unclamped_size = [&] { - // The content-based minimum size of a flex item is the smaller of its specified size suggestion - // and its content size suggestion if its specified size suggestion exists; - if (auto specified_size_suggestion = this->specified_size_suggestion(item); specified_size_suggestion.has_value()) { - return min(specified_size_suggestion.value(), content_size_suggestion(item)); - } - - // otherwise, the smaller of its transferred size suggestion and its content size suggestion - // if the element is replaced and its transferred size suggestion exists; + // The content-based minimum size of a flex item differs depending on whether the flex item is replaced or not: + // -> For replaced elements if (item.box->is_replaced_box()) { + // Use the smaller of the content size suggestion and the transferred size suggestion (if one exists), + // capped by the specified size suggestion (if one exists). + auto size = content_size_suggestion(item); if (auto transferred_size_suggestion = this->transferred_size_suggestion(item); transferred_size_suggestion.has_value()) { - return min(transferred_size_suggestion.value(), content_size_suggestion(item)); + size = min(size, transferred_size_suggestion.value()); } + if (auto specified_size_suggestion = this->specified_size_suggestion(item); specified_size_suggestion.has_value()) { + size = min(size, specified_size_suggestion.value()); + } + return size; } - // otherwise its content size suggestion. - return content_size_suggestion(item); + // -> For non-replaced elements + { + // Use the larger of the content size suggestion and the transferred size suggestion (if one exists), + // capped by the specified size suggestion (if one exists). + auto size = content_size_suggestion(item); + if (auto transferred_size_suggestion = this->transferred_size_suggestion(item); transferred_size_suggestion.has_value()) { + size = max(size, transferred_size_suggestion.value()); + } + if (auto specified_size_suggestion = this->specified_size_suggestion(item); specified_size_suggestion.has_value()) { + size = min(size, specified_size_suggestion.value()); + } + return size; + } }(); - // In all cases, the size is clamped by the maximum main size if it’s definite. + // In either case, the size is clamped by the maximum main size if it’s definite. if (has_main_max_size(item.box)) { return min(unclamped_size, specified_main_max_size(item.box)); } From 84a695c958d82bf32dfc4db83676f3a07215ee31 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 13 Mar 2025 16:04:48 +0000 Subject: [PATCH 003/141] LibWeb/CSS: Evaluate Supports query components during parsing Instead of parsing the parts of a `@supports` query, then only evaluating them when constructing the Supports itself, we can instead evaluate them as we parse them. This simplifies things as we no longer need to pass a Realm around, and don't have to re-parse the conditions again with a new Parser instance. --- Libraries/LibWeb/CSS/Parser/Helpers.cpp | 7 ----- Libraries/LibWeb/CSS/Parser/Parser.cpp | 11 +++++-- Libraries/LibWeb/CSS/Parser/Parser.h | 1 - Libraries/LibWeb/CSS/Supports.cpp | 39 +++++++++---------------- Libraries/LibWeb/CSS/Supports.h | 22 +++++++------- 5 files changed, 32 insertions(+), 48 deletions(-) diff --git a/Libraries/LibWeb/CSS/Parser/Helpers.cpp b/Libraries/LibWeb/CSS/Parser/Helpers.cpp index 9478d33139a..c503f65ba7d 100644 --- a/Libraries/LibWeb/CSS/Parser/Helpers.cpp +++ b/Libraries/LibWeb/CSS/Parser/Helpers.cpp @@ -87,11 +87,4 @@ RefPtr parse_css_supports(CSS::Parser::ParsingParams const& conte return CSS::Parser::Parser::create(context, string).parse_as_supports(); } -Optional parse_css_supports_condition(CSS::Parser::ParsingParams const& context, StringView string) -{ - if (string.is_empty()) - return {}; - return CSS::Parser::Parser::create(context, string).parse_as_supports_condition(); -} - } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index c606f8aefbc..aee37269670 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -154,7 +154,7 @@ RefPtr Parser::parse_a_supports(TokenStream& tokens) m_rule_context.take_last(); token_stream.discard_whitespace(); if (maybe_condition && !token_stream.has_next_token()) - return Supports::create(realm(), maybe_condition.release_nonnull()); + return Supports::create(maybe_condition.release_nonnull()); return {}; } @@ -279,7 +279,9 @@ Optional Parser::parse_supports_feature(TokenStreamto_string() } + Supports::Declaration { + declaration->to_string(), + convert_to_style_property(*declaration).has_value() } }; } } @@ -291,8 +293,11 @@ Optional Parser::parse_supports_feature(TokenStream parse_media_query(CSS::Parser::ParsingParams const&, StringView); Vector> parse_media_query_list(CSS::Parser::ParsingParams const&, StringView); RefPtr parse_css_supports(CSS::Parser::ParsingParams const&, StringView); -Optional parse_css_supports_condition(CSS::Parser::ParsingParams const&, StringView); } diff --git a/Libraries/LibWeb/CSS/Supports.cpp b/Libraries/LibWeb/CSS/Supports.cpp index 5f97ada429a..2106524c40e 100644 --- a/Libraries/LibWeb/CSS/Supports.cpp +++ b/Libraries/LibWeb/CSS/Supports.cpp @@ -1,11 +1,10 @@ /* - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include -#include #include namespace Web::CSS { @@ -16,26 +15,26 @@ static void indent(StringBuilder& builder, int levels) builder.append(" "sv); } -Supports::Supports(JS::Realm& realm, NonnullOwnPtr&& condition) +Supports::Supports(NonnullOwnPtr&& condition) : m_condition(move(condition)) { - m_matches = m_condition->evaluate(realm); + m_matches = m_condition->evaluate(); } -bool Supports::Condition::evaluate(JS::Realm& realm) const +bool Supports::Condition::evaluate() const { switch (type) { case Type::Not: - return !children.first().evaluate(realm); + return !children.first().evaluate(); case Type::And: for (auto& child : children) { - if (!child.evaluate(realm)) + if (!child.evaluate()) return false; } return true; case Type::Or: for (auto& child : children) { - if (child.evaluate(realm)) + if (child.evaluate()) return true; } return false; @@ -43,40 +42,28 @@ bool Supports::Condition::evaluate(JS::Realm& realm) const VERIFY_NOT_REACHED(); } -bool Supports::InParens::evaluate(JS::Realm& realm) const +bool Supports::InParens::evaluate() const { return value.visit( [&](NonnullOwnPtr const& condition) { - return condition->evaluate(realm); + return condition->evaluate(); }, [&](Feature const& feature) { - return feature.evaluate(realm); + return feature.evaluate(); }, [&](GeneralEnclosed const&) { return false; }); } -bool Supports::Declaration::evaluate(JS::Realm& realm) const -{ - auto style_property = parse_css_supports_condition(Parser::ParsingParams { realm }, declaration); - return style_property.has_value(); -} - -bool Supports::Selector::evaluate(JS::Realm& realm) const -{ - auto style_property = parse_selector(Parser::ParsingParams { realm }, selector); - return style_property.has_value(); -} - -bool Supports::Feature::evaluate(JS::Realm& realm) const +bool Supports::Feature::evaluate() const { return value.visit( [&](Declaration const& declaration) { - return declaration.evaluate(realm); + return declaration.evaluate(); }, [&](Selector const& selector) { - return selector.evaluate(realm); + return selector.evaluate(); }); } diff --git a/Libraries/LibWeb/CSS/Supports.h b/Libraries/LibWeb/CSS/Supports.h index 2bd3cb8e098..913ceb24c4b 100644 --- a/Libraries/LibWeb/CSS/Supports.h +++ b/Libraries/LibWeb/CSS/Supports.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -17,26 +17,26 @@ namespace Web::CSS { // https://www.w3.org/TR/css-conditional-3/#at-supports class Supports final : public RefCounted { - friend class Parser::Parser; - public: struct Declaration { String declaration; - [[nodiscard]] bool evaluate(JS::Realm&) const; + bool matches; + [[nodiscard]] bool evaluate() const { return matches; } String to_string() const; void dump(StringBuilder&, int indent_levels = 0) const; }; struct Selector { String selector; - [[nodiscard]] bool evaluate(JS::Realm&) const; + bool matches; + [[nodiscard]] bool evaluate() const { return matches; } String to_string() const; void dump(StringBuilder&, int indent_levels = 0) const; }; struct Feature { Variant value; - [[nodiscard]] bool evaluate(JS::Realm&) const; + [[nodiscard]] bool evaluate() const; String to_string() const; void dump(StringBuilder&, int indent_levels = 0) const; }; @@ -45,7 +45,7 @@ public: struct InParens { Variant, Feature, GeneralEnclosed> value; - [[nodiscard]] bool evaluate(JS::Realm&) const; + [[nodiscard]] bool evaluate() const; String to_string() const; void dump(StringBuilder&, int indent_levels = 0) const; }; @@ -59,14 +59,14 @@ public: Type type; Vector children; - [[nodiscard]] bool evaluate(JS::Realm&) const; + [[nodiscard]] bool evaluate() const; String to_string() const; void dump(StringBuilder&, int indent_levels = 0) const; }; - static NonnullRefPtr create(JS::Realm& realm, NonnullOwnPtr&& condition) + static NonnullRefPtr create(NonnullOwnPtr&& condition) { - return adopt_ref(*new Supports(realm, move(condition))); + return adopt_ref(*new Supports(move(condition))); } bool matches() const { return m_matches; } @@ -75,7 +75,7 @@ public: void dump(StringBuilder&, int indent_levels = 0) const; private: - Supports(JS::Realm&, NonnullOwnPtr&&); + Supports(NonnullOwnPtr&&); NonnullOwnPtr m_condition; bool m_matches { false }; From 0f5e054f979f38813675ec6de24a0849702faee8 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 10:31:27 +0000 Subject: [PATCH 004/141] LibWeb: Implement generic boolean logic for media/supports queries CSS Values 5 now defines a `` type that is used in place of the bespoke grammar that previously existed for `@media` and `@supports` queries. This commit implements some BooleanExpression types to represent the nodes in a ``, and reimplements `@media` and `@supports` queries using this. The one part of this implementation I'm not convinced on is that the `evaluate()` methods take a `HTML::Window*`. This is a compromise because `@media` requires a Window, and `@supports` does not require anything at all. As more users of `` get implemented in the future, it will become clear if this is sufficient, or if we need to do something smarter. As a bonus, this actually improves our serialization of media queries! --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/CSS/BooleanExpression.cpp | 136 ++++++++++++++ Libraries/LibWeb/CSS/BooleanExpression.h | 171 +++++++++++++++++ Libraries/LibWeb/CSS/GeneralEnclosed.h | 85 --------- Libraries/LibWeb/CSS/MediaQuery.cpp | 131 +++---------- Libraries/LibWeb/CSS/MediaQuery.h | 88 +++------ Libraries/LibWeb/CSS/Parser/MediaParsing.cpp | 173 +++--------------- Libraries/LibWeb/CSS/Parser/Parser.cpp | 137 +++++++------- Libraries/LibWeb/CSS/Parser/Parser.h | 17 +- Libraries/LibWeb/CSS/Supports.cpp | 141 ++------------ Libraries/LibWeb/CSS/Supports.h | 100 +++++----- .../css/media-query-serialization-basic.txt | 2 +- .../css/cssom/serialize-media-rule.txt | 7 +- 13 files changed, 526 insertions(+), 663 deletions(-) create mode 100644 Libraries/LibWeb/CSS/BooleanExpression.cpp create mode 100644 Libraries/LibWeb/CSS/BooleanExpression.h delete mode 100644 Libraries/LibWeb/CSS/GeneralEnclosed.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 1f7b520878b..ab4e1a6cc79 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -55,6 +55,7 @@ set(SOURCES Crypto/SubtleCrypto.cpp CSS/Angle.cpp CSS/AnimationEvent.cpp + CSS/BooleanExpression.cpp CSS/CalculatedOr.cpp CSS/Clip.cpp CSS/CountersSet.cpp diff --git a/Libraries/LibWeb/CSS/BooleanExpression.cpp b/Libraries/LibWeb/CSS/BooleanExpression.cpp new file mode 100644 index 00000000000..d8b73d9fe9c --- /dev/null +++ b/Libraries/LibWeb/CSS/BooleanExpression.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::CSS { + +bool BooleanExpression::evaluate_to_boolean(HTML::Window const* window) const +{ + return evaluate(window) == MatchResult::True; +} + +void BooleanExpression::indent(StringBuilder& builder, int levels) +{ + builder.append_repeated(" "sv, levels); +} + +void GeneralEnclosed::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.appendff("GeneralEnclosed: {}\n", to_string()); +} + +MatchResult BooleanNotExpression::evaluate(HTML::Window const* window) const +{ + // https://drafts.csswg.org/css-values-5/#boolean-logic + // `not test` evaluates to true if its contained test is false, false if it’s true, and unknown if it’s unknown. + switch (m_child->evaluate(window)) { + case MatchResult::False: + return MatchResult::True; + case MatchResult::True: + return MatchResult::False; + case MatchResult::Unknown: + return MatchResult::Unknown; + } + VERIFY_NOT_REACHED(); +} + +String BooleanNotExpression::to_string() const +{ + return MUST(String::formatted("not {}", m_child->to_string())); +} + +void BooleanNotExpression::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("NOT:\n"sv); + m_child->dump(builder, indent_levels + 1); +} + +MatchResult BooleanExpressionInParens::evaluate(HTML::Window const* window) const +{ + return m_child->evaluate(window); +} + +String BooleanExpressionInParens::to_string() const +{ + return MUST(String::formatted("({})", m_child->to_string())); +} + +void BooleanExpressionInParens::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("(\n"sv); + m_child->dump(builder, indent_levels + 1); + indent(builder, indent_levels); + builder.append(")\n"sv); +} + +MatchResult BooleanAndExpression::evaluate(HTML::Window const* window) const +{ + // https://drafts.csswg.org/css-values-5/#boolean-logic + // Multiple tests connected with `and` evaluate to true if all of those tests are true, false if any of them are + // false, and unknown otherwise (i.e. if at least one unknown, but no false). + size_t true_results = 0; + for (auto const& child : m_children) { + auto child_match = child->evaluate(window); + if (child_match == MatchResult::False) + return MatchResult::False; + if (child_match == MatchResult::True) + true_results++; + } + if (true_results == m_children.size()) + return MatchResult::True; + return MatchResult::Unknown; +} + +String BooleanAndExpression::to_string() const +{ + return MUST(String::join(" and "sv, m_children)); +} + +void BooleanAndExpression::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("AND:\n"sv); + for (auto const& child : m_children) + child->dump(builder, indent_levels + 1); +} + +MatchResult BooleanOrExpression::evaluate(HTML::Window const* window) const +{ + // https://drafts.csswg.org/css-values-5/#boolean-logic + // Multiple tests connected with `or` evaluate to true if any of those tests are true, false if all of them are + // false, and unknown otherwise (i.e. at least one unknown, but no true). + size_t false_results = 0; + for (auto const& child : m_children) { + auto child_match = child->evaluate(window); + if (child_match == MatchResult::True) + return MatchResult::True; + if (child_match == MatchResult::False) + false_results++; + } + if (false_results == m_children.size()) + return MatchResult::False; + return MatchResult::Unknown; +} + +String BooleanOrExpression::to_string() const +{ + return MUST(String::join(" or "sv, m_children)); +} + +void BooleanOrExpression::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("OR:\n"sv); + for (auto const& child : m_children) + child->dump(builder, indent_levels + 1); +} + +} diff --git a/Libraries/LibWeb/CSS/BooleanExpression.h b/Libraries/LibWeb/CSS/BooleanExpression.h new file mode 100644 index 00000000000..85b86ae032d --- /dev/null +++ b/Libraries/LibWeb/CSS/BooleanExpression.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2021-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::CSS { + +// Corresponds to Kleene 3-valued logic. +enum class MatchResult { + False, + True, + Unknown, +}; + +inline MatchResult as_match_result(bool value) +{ + return value ? MatchResult::True : MatchResult::False; +} + +inline MatchResult negate(MatchResult value) +{ + switch (value) { + case MatchResult::False: + return MatchResult::True; + case MatchResult::True: + return MatchResult::False; + case MatchResult::Unknown: + return MatchResult::Unknown; + } + VERIFY_NOT_REACHED(); +} + +// The contents of this file implement the `` concept. +// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr +class BooleanExpression { +public: + virtual ~BooleanExpression() = default; + + bool evaluate_to_boolean(HTML::Window const*) const; + static void indent(StringBuilder& builder, int levels); + + virtual MatchResult evaluate(HTML::Window const*) const = 0; + virtual String to_string() const = 0; + virtual void dump(StringBuilder&, int indent_levels = 0) const = 0; +}; + +// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed +class GeneralEnclosed final : public BooleanExpression { +public: + static NonnullOwnPtr create(String serialized_contents, MatchResult matches = MatchResult::Unknown) + { + return adopt_own(*new GeneralEnclosed(move(serialized_contents), matches)); + } + virtual ~GeneralEnclosed() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override { return m_matches; } + virtual String to_string() const override { return m_serialized_contents; } + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + GeneralEnclosed(String serialized_contents, MatchResult matches) + : m_serialized_contents(move(serialized_contents)) + , m_matches(matches) + { + } + + String m_serialized_contents; + MatchResult m_matches; +}; + +class BooleanNotExpression final : public BooleanExpression { +public: + static NonnullOwnPtr create(NonnullOwnPtr&& child) + { + return adopt_own(*new BooleanNotExpression(move(child))); + } + virtual ~BooleanNotExpression() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanNotExpression(NonnullOwnPtr&& child) + : m_child(move(child)) + { + } + + NonnullOwnPtr m_child; +}; + +class BooleanExpressionInParens final : public BooleanExpression { +public: + static NonnullOwnPtr create(NonnullOwnPtr&& child) + { + return adopt_own(*new BooleanExpressionInParens(move(child))); + } + virtual ~BooleanExpressionInParens() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanExpressionInParens(NonnullOwnPtr&& child) + : m_child(move(child)) + { + } + + NonnullOwnPtr m_child; +}; + +class BooleanAndExpression final : public BooleanExpression { +public: + static NonnullOwnPtr create(Vector>&& children) + { + return adopt_own(*new BooleanAndExpression(move(children))); + } + virtual ~BooleanAndExpression() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanAndExpression(Vector>&& children) + : m_children(move(children)) + { + } + + Vector> m_children; +}; + +class BooleanOrExpression final : public BooleanExpression { +public: + static NonnullOwnPtr create(Vector>&& children) + { + return adopt_own(*new BooleanOrExpression(move(children))); + } + virtual ~BooleanOrExpression() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanOrExpression(Vector>&& children) + : m_children(move(children)) + { + } + + Vector> m_children; +}; + +} + +template<> +struct AK::Formatter : AK::Formatter { + ErrorOr format(FormatBuilder& builder, Web::CSS::BooleanExpression const& expression) + { + return Formatter::format(builder, expression.to_string()); + } +}; diff --git a/Libraries/LibWeb/CSS/GeneralEnclosed.h b/Libraries/LibWeb/CSS/GeneralEnclosed.h deleted file mode 100644 index 71e3c6eab16..00000000000 --- a/Libraries/LibWeb/CSS/GeneralEnclosed.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2021, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Web::CSS { - -// Corresponds to Kleene 3-valued logic. -// https://www.w3.org/TR/mediaqueries-4/#evaluating -enum class MatchResult { - False, - True, - Unknown, -}; - -inline MatchResult as_match_result(bool value) -{ - return value ? MatchResult::True : MatchResult::False; -} - -inline MatchResult negate(MatchResult value) -{ - switch (value) { - case MatchResult::False: - return MatchResult::True; - case MatchResult::True: - return MatchResult::False; - case MatchResult::Unknown: - return MatchResult::Unknown; - } - VERIFY_NOT_REACHED(); -} - -template -inline MatchResult evaluate_and(Collection& collection, Evaluate evaluate) -{ - size_t true_results = 0; - for (auto& item : collection) { - auto item_match = evaluate(item); - if (item_match == MatchResult::False) - return MatchResult::False; - if (item_match == MatchResult::True) - true_results++; - } - if (true_results == collection.size()) - return MatchResult::True; - return MatchResult::Unknown; -} - -template -inline MatchResult evaluate_or(Collection& collection, Evaluate evaluate) -{ - size_t false_results = 0; - for (auto& item : collection) { - auto item_match = evaluate(item); - if (item_match == MatchResult::True) - return MatchResult::True; - if (item_match == MatchResult::False) - false_results++; - } - if (false_results == collection.size()) - return MatchResult::False; - return MatchResult::Unknown; -} - -// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed -class GeneralEnclosed { -public: - GeneralEnclosed(String serialized_contents) - : m_serialized_contents(move(serialized_contents)) - { - } - - MatchResult evaluate() const { return MatchResult::Unknown; } - String const& to_string() const { return m_serialized_contents; } - -private: - String m_serialized_contents; -}; -} diff --git a/Libraries/LibWeb/CSS/MediaQuery.cpp b/Libraries/LibWeb/CSS/MediaQuery.cpp index bd0b957cd9a..1b36971a34e 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.cpp +++ b/Libraries/LibWeb/CSS/MediaQuery.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -83,57 +83,58 @@ String MediaFeature::to_string() const VERIFY_NOT_REACHED(); } -bool MediaFeature::evaluate(HTML::Window const& window) const +MatchResult MediaFeature::evaluate(HTML::Window const* window) const { - auto maybe_queried_value = window.query_media_feature(m_id); + VERIFY(window); + auto maybe_queried_value = window->query_media_feature(m_id); if (!maybe_queried_value.has_value()) - return false; + return MatchResult::False; auto queried_value = maybe_queried_value.release_value(); CalculationResolutionContext calculation_context { - .length_resolution_context = Length::ResolutionContext::for_window(window), + .length_resolution_context = Length::ResolutionContext::for_window(*window), }; switch (m_type) { case Type::IsTrue: if (queried_value.is_integer()) - return queried_value.integer().resolved(calculation_context) != 0; + return as_match_result(queried_value.integer().resolved(calculation_context) != 0); if (queried_value.is_length()) { auto length = queried_value.length().resolved(calculation_context); - return length->raw_value() != 0; + return as_match_result(length->raw_value() != 0); } // FIXME: I couldn't figure out from the spec how ratios should be evaluated in a boolean context. if (queried_value.is_ratio()) - return !queried_value.ratio().is_degenerate(); + return as_match_result(!queried_value.ratio().is_degenerate()); if (queried_value.is_resolution()) - return queried_value.resolution().resolved(calculation_context).map([](auto& it) { return it.to_dots_per_pixel(); }).value_or(0) != 0; + return as_match_result(queried_value.resolution().resolved(calculation_context).map([](auto& it) { return it.to_dots_per_pixel(); }).value_or(0) != 0); if (queried_value.is_ident()) { // NOTE: It is not technically correct to always treat `no-preference` as false, but every // media-feature that accepts it as a value treats it as false, so good enough. :^) // If other features gain this property for other keywords in the future, we can // add more robust handling for them then. - return queried_value.ident() != Keyword::None - && queried_value.ident() != Keyword::NoPreference; + return as_match_result(queried_value.ident() != Keyword::None + && queried_value.ident() != Keyword::NoPreference); } - return false; + return MatchResult::False; case Type::ExactValue: - return compare(window, *m_value, Comparison::Equal, queried_value); + return as_match_result(compare(*window, *m_value, Comparison::Equal, queried_value)); case Type::MinValue: - return compare(window, queried_value, Comparison::GreaterThanOrEqual, *m_value); + return as_match_result(compare(*window, queried_value, Comparison::GreaterThanOrEqual, *m_value)); case Type::MaxValue: - return compare(window, queried_value, Comparison::LessThanOrEqual, *m_value); + return as_match_result(compare(*window, queried_value, Comparison::LessThanOrEqual, *m_value)); case Type::Range: - if (!compare(window, m_range->left_value, m_range->left_comparison, queried_value)) - return false; + if (!compare(*window, m_range->left_value, m_range->left_comparison, queried_value)) + return MatchResult::False; if (m_range->right_comparison.has_value()) - if (!compare(window, queried_value, *m_range->right_comparison, *m_range->right_value)) - return false; + if (!compare(*window, queried_value, *m_range->right_comparison, *m_range->right_value)) + return MatchResult::False; - return true; + return MatchResult::True; } VERIFY_NOT_REACHED(); @@ -247,92 +248,10 @@ bool MediaFeature::compare(HTML::Window const& window, MediaFeatureValue left, C VERIFY_NOT_REACHED(); } -NonnullOwnPtr MediaCondition::from_general_enclosed(GeneralEnclosed&& general_enclosed) +void MediaFeature::dump(StringBuilder& builder, int indent_levels) const { - auto result = new MediaCondition; - result->type = Type::GeneralEnclosed; - result->general_enclosed = move(general_enclosed); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_feature(MediaFeature&& feature) -{ - auto result = new MediaCondition; - result->type = Type::Single; - result->feature = move(feature); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_not(NonnullOwnPtr&& condition) -{ - auto result = new MediaCondition; - result->type = Type::Not; - result->conditions.append(move(condition)); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_and_list(Vector>&& conditions) -{ - auto result = new MediaCondition; - result->type = Type::And; - result->conditions = move(conditions); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_or_list(Vector>&& conditions) -{ - auto result = new MediaCondition; - result->type = Type::Or; - result->conditions = move(conditions); - - return adopt_own(*result); -} - -String MediaCondition::to_string() const -{ - StringBuilder builder; - builder.append('('); - switch (type) { - case Type::Single: - builder.append(feature->to_string()); - break; - case Type::Not: - builder.append("not "sv); - builder.append(conditions.first()->to_string()); - break; - case Type::And: - builder.join(" and "sv, conditions); - break; - case Type::Or: - builder.join(" or "sv, conditions); - break; - case Type::GeneralEnclosed: - builder.append(general_enclosed->to_string()); - break; - } - builder.append(')'); - return MUST(builder.to_string()); -} - -MatchResult MediaCondition::evaluate(HTML::Window const& window) const -{ - switch (type) { - case Type::Single: - return as_match_result(feature->evaluate(window)); - case Type::Not: - return negate(conditions.first()->evaluate(window)); - case Type::And: - return evaluate_and(conditions, [&](auto& child) { return child->evaluate(window); }); - case Type::Or: - return evaluate_or(conditions, [&](auto& child) { return child->evaluate(window); }); - case Type::GeneralEnclosed: - return general_enclosed->evaluate(); - } - VERIFY_NOT_REACHED(); + indent(builder, indent_levels); + builder.appendff("MediaFeature: {}", to_string()); } String MediaQuery::to_string() const @@ -386,7 +305,7 @@ bool MediaQuery::evaluate(HTML::Window const& window) MatchResult result = matches_media(m_media_type); if ((result == MatchResult::True) && m_media_condition) - result = m_media_condition->evaluate(window); + result = m_media_condition->evaluate(&window); if (m_negated) result = negate(result); diff --git a/Libraries/LibWeb/CSS/MediaQuery.h b/Libraries/LibWeb/CSS/MediaQuery.h index 72b5f0a8949..0e2e84e43b1 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.h +++ b/Libraries/LibWeb/CSS/MediaQuery.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,8 +10,8 @@ #include #include #include +#include #include -#include #include #include @@ -94,7 +94,7 @@ private: }; // https://www.w3.org/TR/mediaqueries-4/#mq-features -class MediaFeature { +class MediaFeature final : public BooleanExpression { public: enum class Comparison { Equal, @@ -105,51 +105,52 @@ public: }; // Corresponds to `` grammar - static MediaFeature boolean(MediaFeatureID id) + static NonnullOwnPtr boolean(MediaFeatureID id) { - return MediaFeature(Type::IsTrue, id); + return adopt_own(*new MediaFeature(Type::IsTrue, id)); } // Corresponds to `` grammar - static MediaFeature plain(MediaFeatureID id, MediaFeatureValue value) + static NonnullOwnPtr plain(MediaFeatureID id, MediaFeatureValue value) { - return MediaFeature(Type::ExactValue, move(id), move(value)); + return adopt_own(*new MediaFeature(Type::ExactValue, move(id), move(value))); } - static MediaFeature min(MediaFeatureID id, MediaFeatureValue value) + static NonnullOwnPtr min(MediaFeatureID id, MediaFeatureValue value) { - return MediaFeature(Type::MinValue, id, move(value)); + return adopt_own(*new MediaFeature(Type::MinValue, id, move(value))); } - static MediaFeature max(MediaFeatureID id, MediaFeatureValue value) + static NonnullOwnPtr max(MediaFeatureID id, MediaFeatureValue value) { - return MediaFeature(Type::MaxValue, id, move(value)); + return adopt_own(*new MediaFeature(Type::MaxValue, id, move(value))); } // Corresponds to `` grammar, with a single comparison - static MediaFeature half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) + static NonnullOwnPtr half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) { - MediaFeature feature { Type::Range, id }; - feature.m_range = Range { - .left_value = value, + auto feature = adopt_own(*new MediaFeature(Type::Range, id)); + feature->m_range = Range { + .left_value = move(value), .left_comparison = comparison, }; return feature; } // Corresponds to `` grammar, with two comparisons - static MediaFeature range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value) + static NonnullOwnPtr range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value) { - MediaFeature feature { Type::Range, id }; - feature.m_range = Range { - .left_value = left_value, + auto feature = adopt_own(*new MediaFeature(Type::Range, id)); + feature->m_range = Range { + .left_value = move(left_value), .left_comparison = left_comparison, .right_comparison = right_comparison, - .right_value = right_value, + .right_value = move(right_value), }; return feature; } - bool evaluate(HTML::Window const&) const; - String to_string() const; + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; private: enum class Type { @@ -182,39 +183,6 @@ private: Optional m_range {}; }; -// https://www.w3.org/TR/mediaqueries-4/#media-conditions -struct MediaCondition { - enum class Type { - Single, - And, - Or, - Not, - GeneralEnclosed, - }; - - // Only used in parsing - enum class AllowOr { - No = 0, - Yes = 1, - }; - - static NonnullOwnPtr from_general_enclosed(GeneralEnclosed&&); - static NonnullOwnPtr from_feature(MediaFeature&&); - static NonnullOwnPtr from_not(NonnullOwnPtr&&); - static NonnullOwnPtr from_and_list(Vector>&&); - static NonnullOwnPtr from_or_list(Vector>&&); - - MatchResult evaluate(HTML::Window const&) const; - String to_string() const; - -private: - MediaCondition() = default; - Type type; - Optional feature; - Vector> conditions; - Optional general_enclosed; -}; - class MediaQuery : public RefCounted { friend class Parser::Parser; @@ -252,7 +220,7 @@ private: // https://www.w3.org/TR/mediaqueries-4/#mq-not bool m_negated { false }; MediaType m_media_type { MediaType::All }; - OwnPtr m_media_condition { nullptr }; + OwnPtr m_media_condition { nullptr }; // Cached value, updated by evaluate() bool m_matches { false }; @@ -275,14 +243,6 @@ struct Formatter : Formatter { } }; -template<> -struct Formatter : Formatter { - ErrorOr format(FormatBuilder& builder, Web::CSS::MediaCondition const& media_condition) - { - return Formatter::format(builder, media_condition.to_string()); - } -}; - template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::MediaQuery const& media_query) diff --git a/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp b/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp index 3a017d7768d..579e83ad564 100644 --- a/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp @@ -96,11 +96,11 @@ NonnullRefPtr Parser::parse_media_query(TokenStream& tokens.discard_whitespace(); // `` - if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) { + if (auto media_condition = parse_media_condition(tokens)) { tokens.discard_whitespace(); if (tokens.has_next_token()) return invalid_media_query(); - media_query->m_media_condition = move(media_condition); + media_query->m_media_condition = media_condition.release_nonnull(); return media_query; } @@ -127,7 +127,11 @@ NonnullRefPtr Parser::parse_media_query(TokenStream& // `[ and ]?` if (auto const& maybe_and = tokens.consume_a_token(); maybe_and.is_ident("and"sv)) { - if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::No)) { + if (auto media_condition = parse_media_condition(tokens)) { + // "or" is disallowed at the top level + if (is(*media_condition)) + return invalid_media_query(); + tokens.discard_whitespace(); if (tokens.has_next_token()) return invalid_media_query(); @@ -141,115 +145,15 @@ NonnullRefPtr Parser::parse_media_query(TokenStream& } // ``, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition -// ``, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition-without-or -// (We distinguish between these two with the `allow_or` parameter.) -OwnPtr Parser::parse_media_condition(TokenStream& tokens, MediaCondition::AllowOr allow_or) +OwnPtr Parser::parse_media_condition(TokenStream& tokens) { - // ` | [ * | * ]` - auto transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - // ` = not ` - auto parse_media_not = [&](auto& tokens) -> OwnPtr { - auto local_transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - auto& first_token = tokens.consume_a_token(); - if (first_token.is_ident("not"sv)) { - if (auto child_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) { - local_transaction.commit(); - return MediaCondition::from_not(child_condition.release_nonnull()); - } - } - - return {}; - }; - - auto parse_media_with_combinator = [&](auto& tokens, StringView combinator) -> OwnPtr { - auto local_transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - auto& first = tokens.consume_a_token(); - if (first.is_ident(combinator)) { - tokens.discard_whitespace(); - if (auto media_in_parens = parse_media_in_parens(tokens)) { - local_transaction.commit(); - return media_in_parens; - } - } - - return {}; - }; - - // ` = and ` - auto parse_media_and = [&](auto& tokens) { return parse_media_with_combinator(tokens, "and"sv); }; - // ` = or ` - auto parse_media_or = [&](auto& tokens) { return parse_media_with_combinator(tokens, "or"sv); }; - - // `` - if (auto maybe_media_not = parse_media_not(tokens)) { - transaction.commit(); - return maybe_media_not.release_nonnull(); - } - - // ` [ * | * ]` - if (auto maybe_media_in_parens = parse_media_in_parens(tokens)) { - tokens.discard_whitespace(); - // Only `` - if (!tokens.has_next_token()) { - transaction.commit(); - return maybe_media_in_parens.release_nonnull(); - } - - Vector> child_conditions; - child_conditions.append(maybe_media_in_parens.release_nonnull()); - - // `*` - if (auto media_and = parse_media_and(tokens)) { - child_conditions.append(media_and.release_nonnull()); - - tokens.discard_whitespace(); - while (tokens.has_next_token()) { - if (auto next_media_and = parse_media_and(tokens)) { - child_conditions.append(next_media_and.release_nonnull()); - tokens.discard_whitespace(); - continue; - } - // We failed - invalid syntax! - return {}; - } - - transaction.commit(); - return MediaCondition::from_and_list(move(child_conditions)); - } - - // `*` - if (allow_or == MediaCondition::AllowOr::Yes) { - if (auto media_or = parse_media_or(tokens)) { - child_conditions.append(media_or.release_nonnull()); - - tokens.discard_whitespace(); - while (tokens.has_next_token()) { - if (auto next_media_or = parse_media_or(tokens)) { - child_conditions.append(next_media_or.release_nonnull()); - tokens.discard_whitespace(); - continue; - } - // We failed - invalid syntax! - return {}; - } - - transaction.commit(); - return MediaCondition::from_or_list(move(child_conditions)); - } - } - } - - return {}; + return parse_boolean_expression(tokens, MatchResult::Unknown, [this](TokenStream& tokens) -> OwnPtr { + return parse_media_feature(tokens); + }); } // ``, https://www.w3.org/TR/mediaqueries-4/#typedef-media-feature -Optional Parser::parse_media_feature(TokenStream& tokens) +OwnPtr Parser::parse_media_feature(TokenStream& tokens) { // `[ | | ]` tokens.discard_whitespace(); @@ -288,7 +192,7 @@ Optional Parser::parse_media_feature(TokenStream& }; // ` = ` - auto parse_mf_boolean = [&](auto& tokens) -> Optional { + auto parse_mf_boolean = [&](auto& tokens) -> OwnPtr { auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); @@ -304,7 +208,7 @@ Optional Parser::parse_media_feature(TokenStream& }; // ` = : ` - auto parse_mf_plain = [&](auto& tokens) -> Optional { + auto parse_mf_plain = [&](auto& tokens) -> OwnPtr { auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); @@ -406,7 +310,7 @@ Optional Parser::parse_media_feature(TokenStream& // | // | // | ` - auto parse_mf_range = [&](auto& tokens) -> Optional { + auto parse_mf_range = [&](auto& tokens) -> OwnPtr { auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); @@ -490,14 +394,14 @@ Optional Parser::parse_media_feature(TokenStream& return {}; }; - if (auto maybe_mf_boolean = parse_mf_boolean(tokens); maybe_mf_boolean.has_value()) - return maybe_mf_boolean.release_value(); + if (auto maybe_mf_boolean = parse_mf_boolean(tokens)) + return maybe_mf_boolean.release_nonnull(); - if (auto maybe_mf_plain = parse_mf_plain(tokens); maybe_mf_plain.has_value()) - return maybe_mf_plain.release_value(); + if (auto maybe_mf_plain = parse_mf_plain(tokens)) + return maybe_mf_plain.release_nonnull(); - if (auto maybe_mf_range = parse_mf_range(tokens); maybe_mf_range.has_value()) - return maybe_mf_range.release_value(); + if (auto maybe_mf_range = parse_mf_range(tokens)) + return maybe_mf_range.release_nonnull(); return {}; } @@ -517,41 +421,6 @@ Optional Parser::parse_media_type(TokenStream`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-in-parens -OwnPtr Parser::parse_media_in_parens(TokenStream& tokens) -{ - // ` = ( ) | ( ) | ` - auto transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - // `( ) | ( )` - auto const& first_token = tokens.next_token(); - if (first_token.is_block() && first_token.block().is_paren()) { - TokenStream inner_token_stream { first_token.block().value }; - if (auto maybe_media_condition = parse_media_condition(inner_token_stream, MediaCondition::AllowOr::Yes)) { - tokens.discard_a_token(); - transaction.commit(); - return maybe_media_condition.release_nonnull(); - } - if (auto maybe_media_feature = parse_media_feature(inner_token_stream); maybe_media_feature.has_value()) { - tokens.discard_a_token(); - transaction.commit(); - return MediaCondition::from_feature(maybe_media_feature.release_value()); - } - } - - // `` - // FIXME: We should only be taking this branch if the grammar doesn't match the above options. - // Currently we take it if the above fail to parse, which is different. - // eg, `@media (min-width: 76yaks)` is valid grammar, but does not parse because `yaks` isn't a unit. - if (auto maybe_general_enclosed = parse_general_enclosed(tokens); maybe_general_enclosed.has_value()) { - transaction.commit(); - return MediaCondition::from_general_enclosed(maybe_general_enclosed.release_value()); - } - - return {}; -} - // ``, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value Optional Parser::parse_media_feature_value(MediaFeatureID media_feature, TokenStream& tokens) { diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index aee37269670..dd095b919c2 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -150,7 +150,7 @@ RefPtr Parser::parse_a_supports(TokenStream& tokens) auto component_values = parse_a_list_of_component_values(tokens); TokenStream token_stream { component_values }; m_rule_context.append(ContextType::SupportsCondition); - auto maybe_condition = parse_supports_condition(token_stream); + auto maybe_condition = parse_boolean_expression(token_stream, MatchResult::False, [this](auto& tokens) { return parse_supports_feature(tokens); }); m_rule_context.take_last(); token_stream.discard_whitespace(); if (maybe_condition && !token_stream.has_next_token()) @@ -159,59 +159,66 @@ RefPtr Parser::parse_a_supports(TokenStream& tokens) return {}; } -OwnPtr Parser::parse_supports_condition(TokenStream& tokens) +// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr +OwnPtr Parser::parse_boolean_expression(TokenStream& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test) { + // ]> = not | + // [ [ and ]* + // | [ or ]* ] + auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& peeked_token = tokens.next_token(); - // `not ` + // `not ` if (peeked_token.is_ident("not"sv)) { tokens.discard_a_token(); tokens.discard_whitespace(); - auto child = parse_supports_in_parens(tokens); - if (!child.has_value()) - return {}; - transaction.commit(); - auto condition = make(); - condition->type = Supports::Condition::Type::Not; - condition->children.append(child.release_value()); - return condition; + if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) { + transaction.commit(); + return BooleanNotExpression::create(child.release_nonnull()); + } + return {}; } - // ` [ and ]* - // | [ or ]*` - Vector children; - Optional condition_type {}; - auto as_condition_type = [](auto& token) -> Optional { + // ` + // [ [ and ]* + // | [ or ]* ]` + Vector> children; + enum class Combinator : u8 { + And, + Or, + }; + Optional combinator; + auto as_combinator = [](auto& token) -> Optional { if (!token.is(Token::Type::Ident)) return {}; auto ident = token.token().ident(); if (ident.equals_ignoring_ascii_case("and"sv)) - return Supports::Condition::Type::And; + return Combinator::And; if (ident.equals_ignoring_ascii_case("or"sv)) - return Supports::Condition::Type::Or; + return Combinator::Or; return {}; }; while (tokens.has_next_token()) { if (!children.is_empty()) { // Expect `and` or `or` here - auto maybe_combination = as_condition_type(tokens.consume_a_token()); - if (!maybe_combination.has_value()) + auto maybe_combinator = as_combinator(tokens.consume_a_token()); + if (!maybe_combinator.has_value()) return {}; - if (!condition_type.has_value()) { - condition_type = maybe_combination.value(); - } else if (maybe_combination != condition_type) { + if (!combinator.has_value()) { + combinator = maybe_combinator.value(); + } else if (maybe_combinator != combinator) { return {}; } } tokens.discard_whitespace(); - if (auto in_parens = parse_supports_in_parens(tokens); in_parens.has_value()) { - children.append(in_parens.release_value()); + if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) { + children.append(child.release_nonnull()); } else { return {}; } @@ -223,15 +230,24 @@ OwnPtr Parser::parse_supports_condition(TokenStream(); - condition->type = condition_type.value_or(Supports::Condition::Type::Or); - condition->children = move(children); - return condition; + if (children.size() == 1) + return children.take_first(); + + VERIFY(combinator.has_value()); + switch (*combinator) { + case Combinator::And: + return BooleanAndExpression::create(move(children)); + case Combinator::Or: + return BooleanOrExpression::create(move(children)); + } + VERIFY_NOT_REACHED(); } -Optional Parser::parse_supports_in_parens(TokenStream& tokens) +OwnPtr Parser::parse_boolean_expression_group(TokenStream& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test) { - // `( )` + // = | ( ]> ) | + + // `( ]> )` auto const& first_token = tokens.next_token(); if (first_token.is_block() && first_token.block().is_paren()) { auto transaction = tokens.begin_transaction(); @@ -239,54 +255,46 @@ Optional Parser::parse_supports_in_parens(TokenStream` - if (auto feature = parse_supports_feature(tokens); feature.has_value()) { - return Supports::InParens { - .value = { feature.release_value() } - }; - } + // `` + if (auto test = parse_test(tokens)) + return test.release_nonnull(); // `` - if (auto general_enclosed = parse_general_enclosed(tokens); general_enclosed.has_value()) { - return Supports::InParens { - .value = general_enclosed.release_value() - }; - } + if (auto general_enclosed = parse_general_enclosed(tokens, result_for_general_enclosed)) + return general_enclosed.release_nonnull(); return {}; } -Optional Parser::parse_supports_feature(TokenStream& tokens) +// https://drafts.csswg.org/css-conditional-4/#typedef-supports-feature +OwnPtr Parser::parse_supports_feature(TokenStream& tokens) { + // = | auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& first_token = tokens.consume_a_token(); - // `` + // ` = ( )` if (first_token.is_block() && first_token.block().is_paren()) { TokenStream block_tokens { first_token.block().value }; // FIXME: Parsing and then converting back to a string is weird. if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) { transaction.commit(); - return Supports::Feature { - Supports::Declaration { - declaration->to_string(), - convert_to_style_property(*declaration).has_value() } - }; + return Supports::Declaration::create( + declaration->to_string(), + convert_to_style_property(*declaration).has_value()); } } - // `` + // ` = selector( )` if (first_token.is_function("selector"sv)) { // FIXME: Parsing and then converting back to a string is weird. StringBuilder builder; @@ -294,19 +302,18 @@ Optional Parser::parse_supports_feature(TokenStream Parser::parse_general_enclosed(TokenStream& tokens) +OwnPtr Parser::parse_general_enclosed(TokenStream& tokens, MatchResult result) { + // FIXME: syntax changed in MediaQueries-5 auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& first_token = tokens.consume_a_token(); @@ -314,13 +321,13 @@ Optional Parser::parse_general_enclosed(TokenStream ? ) ]` if (first_token.is_function()) { transaction.commit(); - return GeneralEnclosed { first_token.to_string() }; + return GeneralEnclosed::create(first_token.to_string(), result); } // `( ? )` if (first_token.is_block() && first_token.block().is_paren()) { transaction.commit(); - return GeneralEnclosed { first_token.to_string() }; + return GeneralEnclosed::create(first_token.to_string(), result); } return {}; @@ -1641,10 +1648,10 @@ LengthOrCalculated Parser::parse_as_sizes_attribute(DOM::Element const& element, // 5. Parse the remaining component values in unparsed size as a . // If it does not parse correctly, or it does parse correctly but the evaluates to false, continue. - TokenStream token_stream { unparsed_size }; - auto media_condition = parse_media_condition(token_stream, MediaCondition::AllowOr::Yes); + TokenStream token_stream { unparsed_size }; + auto media_condition = parse_media_condition(token_stream); auto const* context_window = window(); - if (!media_condition || (context_window && media_condition->evaluate(*context_window) == MatchResult::False)) { + if (!media_condition || (context_window && media_condition->evaluate(context_window) == MatchResult::False)) { continue; } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 551ae93416c..99cfa4c59ef 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -13,9 +13,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -215,7 +215,7 @@ private: void consume_a_function_and_do_nothing(TokenStream&); // TODO: consume_a_unicode_range_value() - Optional parse_general_enclosed(TokenStream&); + OwnPtr parse_general_enclosed(TokenStream&, MatchResult); template Vector parse_font_face_src(TokenStream&); @@ -434,15 +434,16 @@ private: ParseErrorOr> parse_simple_selector(TokenStream&); NonnullRefPtr parse_media_query(TokenStream&); - OwnPtr parse_media_condition(TokenStream&, MediaCondition::AllowOr allow_or); - Optional parse_media_feature(TokenStream&); + OwnPtr parse_media_condition(TokenStream&); + OwnPtr parse_media_feature(TokenStream&); Optional parse_media_type(TokenStream&); - OwnPtr parse_media_in_parens(TokenStream&); Optional parse_media_feature_value(MediaFeatureID, TokenStream&); - OwnPtr parse_supports_condition(TokenStream&); - Optional parse_supports_in_parens(TokenStream&); - Optional parse_supports_feature(TokenStream&); + using ParseTest = AK::Function(TokenStream&)> const&; + OwnPtr parse_boolean_expression(TokenStream&, MatchResult result_for_general_enclosed, ParseTest parse_test); + OwnPtr parse_boolean_expression_group(TokenStream&, MatchResult result_for_general_enclosed, ParseTest parse_test); + + OwnPtr parse_supports_feature(TokenStream&); NonnullRefPtr resolve_unresolved_style_value(DOM::Element&, Optional, PropertyID, UnresolvedStyleValue const&); bool expand_variables(DOM::Element&, Optional, FlyString const& property_name, HashMap>& dependencies, TokenStream& source, Vector& dest); diff --git a/Libraries/LibWeb/CSS/Supports.cpp b/Libraries/LibWeb/CSS/Supports.cpp index 2106524c40e..583074a003f 100644 --- a/Libraries/LibWeb/CSS/Supports.cpp +++ b/Libraries/LibWeb/CSS/Supports.cpp @@ -9,150 +9,47 @@ namespace Web::CSS { -static void indent(StringBuilder& builder, int levels) -{ - for (int i = 0; i < levels; i++) - builder.append(" "sv); -} - -Supports::Supports(NonnullOwnPtr&& condition) +Supports::Supports(NonnullOwnPtr&& condition) : m_condition(move(condition)) { - m_matches = m_condition->evaluate(); + m_matches = m_condition->evaluate_to_boolean(nullptr); } -bool Supports::Condition::evaluate() const +MatchResult Supports::Declaration::evaluate(HTML::Window const*) const { - switch (type) { - case Type::Not: - return !children.first().evaluate(); - case Type::And: - for (auto& child : children) { - if (!child.evaluate()) - return false; - } - return true; - case Type::Or: - for (auto& child : children) { - if (child.evaluate()) - return true; - } - return false; - } - VERIFY_NOT_REACHED(); -} - -bool Supports::InParens::evaluate() const -{ - return value.visit( - [&](NonnullOwnPtr const& condition) { - return condition->evaluate(); - }, - [&](Feature const& feature) { - return feature.evaluate(); - }, - [&](GeneralEnclosed const&) { - return false; - }); -} - -bool Supports::Feature::evaluate() const -{ - return value.visit( - [&](Declaration const& declaration) { - return declaration.evaluate(); - }, - [&](Selector const& selector) { - return selector.evaluate(); - }); + return as_match_result(m_matches); } String Supports::Declaration::to_string() const { - return MUST(String::formatted("({})", declaration)); -} - -String Supports::Selector::to_string() const -{ - return MUST(String::formatted("selector({})", selector)); -} - -String Supports::Feature::to_string() const -{ - return value.visit([](auto& it) { return it.to_string(); }); -} - -String Supports::InParens::to_string() const -{ - return value.visit( - [](NonnullOwnPtr const& condition) { return MUST(String::formatted("({})", condition->to_string())); }, - [](Supports::Feature const& it) { return it.to_string(); }, - [](GeneralEnclosed const& it) { return it.to_string(); }); -} - -String Supports::Condition::to_string() const -{ - switch (type) { - case Type::Not: - return MUST(String::formatted("not {}", children.first().to_string())); - case Type::And: - return MUST(String::join(" and "sv, children)); - case Type::Or: - return MUST(String::join(" or "sv, children)); - } - VERIFY_NOT_REACHED(); -} - -String Supports::to_string() const -{ - return m_condition->to_string(); + return MUST(String::formatted("({})", m_declaration)); } void Supports::Declaration::dump(StringBuilder& builder, int indent_levels) const { indent(builder, indent_levels); - builder.appendff("Declaration: {}\n", declaration); + builder.appendff("Declaration: `{}`, matches={}\n", m_declaration, m_matches); +} + +MatchResult Supports::Selector::evaluate(HTML::Window const*) const +{ + return as_match_result(m_matches); +} + +String Supports::Selector::to_string() const +{ + return MUST(String::formatted("selector({})", m_selector)); } void Supports::Selector::dump(StringBuilder& builder, int indent_levels) const { indent(builder, indent_levels); - builder.appendff("Selector: {}\n", selector); + builder.appendff("Selector: `{}` matches={}\n", m_selector, m_matches); } -void Supports::Feature::dump(StringBuilder& builder, int indent_levels) const +String Supports::to_string() const { - value.visit([&](auto& it) { it.dump(builder, indent_levels); }); -} - -void Supports::InParens::dump(StringBuilder& builder, int indent_levels) const -{ - value.visit( - [&](NonnullOwnPtr const& condition) { condition->dump(builder, indent_levels); }, - [&](Supports::Feature const& it) { it.dump(builder, indent_levels); }, - [&](GeneralEnclosed const& it) { - indent(builder, indent_levels); - builder.appendff("GeneralEnclosed: {}\n", it.to_string()); - }); -} - -void Supports::Condition::dump(StringBuilder& builder, int indent_levels) const -{ - indent(builder, indent_levels); - StringView type_name = [](Type type) { - switch (type) { - case Type::And: - return "AND"sv; - case Type::Or: - return "OR"sv; - case Type::Not: - return "NOT"sv; - } - VERIFY_NOT_REACHED(); - }(type); - builder.appendff("Condition: {}\n", type_name); - for (auto const& child : children) - child.dump(builder, indent_levels + 1); + return m_condition->to_string(); } void Supports::dump(StringBuilder& builder, int indent_levels) const diff --git a/Libraries/LibWeb/CSS/Supports.h b/Libraries/LibWeb/CSS/Supports.h index 913ceb24c4b..8c9fd403df2 100644 --- a/Libraries/LibWeb/CSS/Supports.h +++ b/Libraries/LibWeb/CSS/Supports.h @@ -9,62 +9,58 @@ #include #include #include -#include -#include -#include +#include namespace Web::CSS { // https://www.w3.org/TR/css-conditional-3/#at-supports class Supports final : public RefCounted { public: - struct Declaration { - String declaration; - bool matches; - [[nodiscard]] bool evaluate() const { return matches; } - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; + class Declaration final : public BooleanExpression { + public: + static NonnullOwnPtr create(String declaration, bool matches) + { + return adopt_own(*new Declaration(move(declaration), matches)); + } + virtual ~Declaration() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + + private: + Declaration(String declaration, bool matches) + : m_declaration(move(declaration)) + , m_matches(matches) + { + } + String m_declaration; + bool m_matches; }; - struct Selector { - String selector; - bool matches; - [[nodiscard]] bool evaluate() const { return matches; } - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; + class Selector final : public BooleanExpression { + public: + static NonnullOwnPtr create(String selector, bool matches) + { + return adopt_own(*new Selector(move(selector), matches)); + } + virtual ~Selector() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + + private: + Selector(String selector, bool matches) + : m_selector(move(selector)) + , m_matches(matches) + { + } + String m_selector; + bool m_matches; }; - struct Feature { - Variant value; - [[nodiscard]] bool evaluate() const; - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; - }; - - struct Condition; - struct InParens { - Variant, Feature, GeneralEnclosed> value; - - [[nodiscard]] bool evaluate() const; - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; - }; - - struct Condition { - enum class Type { - Not, - And, - Or, - }; - Type type; - Vector children; - - [[nodiscard]] bool evaluate() const; - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; - }; - - static NonnullRefPtr create(NonnullOwnPtr&& condition) + static NonnullRefPtr create(NonnullOwnPtr&& condition) { return adopt_ref(*new Supports(move(condition))); } @@ -75,18 +71,10 @@ public: void dump(StringBuilder&, int indent_levels = 0) const; private: - Supports(NonnullOwnPtr&&); + Supports(NonnullOwnPtr&&); - NonnullOwnPtr m_condition; + NonnullOwnPtr m_condition; bool m_matches { false }; }; } - -template<> -struct AK::Formatter : AK::Formatter { - ErrorOr format(FormatBuilder& builder, Web::CSS::Supports::InParens const& in_parens) - { - return Formatter::format(builder, in_parens.to_string()); - } -}; diff --git a/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt b/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt index a36ee22bd83..300c90b8cd3 100644 --- a/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt +++ b/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt @@ -1,6 +1,6 @@ @media screen { } -@media screen and ((min-width: 20px) and (max-width: 40px)) { +@media screen and (min-width: 20px) and (max-width: 40px) { } @media screen and (min-resolution: 1dppx) { } diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt index 5e1e84f8836..3b36125ba71 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt @@ -2,8 +2,7 @@ Harness status: OK Found 12 tests -10 Pass -2 Fail +12 Pass Pass empty media query list Pass type - no features Pass type - no features - negation @@ -11,8 +10,8 @@ Pass type - no features - character case normalization Pass type - omission of all Pass type - inclusion of negated all Pass features - character case normalization -Fail features - preservation of overspecified features -Fail features - no lexicographical sorting +Pass features - preservation of overspecified features +Pass features - no lexicographical sorting Pass media query list Pass one rule Pass many rules \ No newline at end of file From e125ab360e2c90b84097daddeac668efdef274f0 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 10:44:22 +0000 Subject: [PATCH 005/141] LibWeb/CSS: Merge MediaFeature data members A MediaFeature either has a MediaFeatureValue, or a Range, or nothing. Combining these into a Variant reduces the size from 176 bytes to 128, and also makes constructing these a little less awkward. --- Libraries/LibWeb/CSS/MediaQuery.cpp | 32 ++++++++++--------- Libraries/LibWeb/CSS/MediaQuery.h | 49 ++++++++++++++--------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/Libraries/LibWeb/CSS/MediaQuery.cpp b/Libraries/LibWeb/CSS/MediaQuery.cpp index 1b36971a34e..87cd73a854a 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.cpp +++ b/Libraries/LibWeb/CSS/MediaQuery.cpp @@ -68,16 +68,18 @@ String MediaFeature::to_string() const case Type::IsTrue: return MUST(String::from_utf8(string_from_media_feature_id(m_id))); case Type::ExactValue: - return MUST(String::formatted("{}: {}", string_from_media_feature_id(m_id), m_value->to_string())); + return MUST(String::formatted("{}: {}", string_from_media_feature_id(m_id), value().to_string())); case Type::MinValue: - return MUST(String::formatted("min-{}: {}", string_from_media_feature_id(m_id), m_value->to_string())); + return MUST(String::formatted("min-{}: {}", string_from_media_feature_id(m_id), value().to_string())); case Type::MaxValue: - return MUST(String::formatted("max-{}: {}", string_from_media_feature_id(m_id), m_value->to_string())); - case Type::Range: - if (!m_range->right_comparison.has_value()) - return MUST(String::formatted("{} {} {}", m_range->left_value.to_string(), comparison_string(m_range->left_comparison), string_from_media_feature_id(m_id))); + return MUST(String::formatted("max-{}: {}", string_from_media_feature_id(m_id), value().to_string())); + case Type::Range: { + auto& range = this->range(); + if (!range.right_comparison.has_value()) + return MUST(String::formatted("{} {} {}", range.left_value.to_string(), comparison_string(range.left_comparison), string_from_media_feature_id(m_id))); - return MUST(String::formatted("{} {} {} {} {}", m_range->left_value.to_string(), comparison_string(m_range->left_comparison), string_from_media_feature_id(m_id), comparison_string(*m_range->right_comparison), m_range->right_value->to_string())); + return MUST(String::formatted("{} {} {} {} {}", range.left_value.to_string(), comparison_string(range.left_comparison), string_from_media_feature_id(m_id), comparison_string(*range.right_comparison), range.right_value->to_string())); + } } VERIFY_NOT_REACHED(); @@ -118,24 +120,26 @@ MatchResult MediaFeature::evaluate(HTML::Window const* window) const return MatchResult::False; case Type::ExactValue: - return as_match_result(compare(*window, *m_value, Comparison::Equal, queried_value)); + return as_match_result(compare(*window, value(), Comparison::Equal, queried_value)); case Type::MinValue: - return as_match_result(compare(*window, queried_value, Comparison::GreaterThanOrEqual, *m_value)); + return as_match_result(compare(*window, queried_value, Comparison::GreaterThanOrEqual, value())); case Type::MaxValue: - return as_match_result(compare(*window, queried_value, Comparison::LessThanOrEqual, *m_value)); + return as_match_result(compare(*window, queried_value, Comparison::LessThanOrEqual, value())); - case Type::Range: - if (!compare(*window, m_range->left_value, m_range->left_comparison, queried_value)) + case Type::Range: { + auto& range = this->range(); + if (!compare(*window, range.left_value, range.left_comparison, queried_value)) return MatchResult::False; - if (m_range->right_comparison.has_value()) - if (!compare(*window, queried_value, *m_range->right_comparison, *m_range->right_value)) + if (range.right_comparison.has_value()) + if (!compare(*window, queried_value, *range.right_comparison, *range.right_value)) return MatchResult::False; return MatchResult::True; } + } VERIFY_NOT_REACHED(); } diff --git a/Libraries/LibWeb/CSS/MediaQuery.h b/Libraries/LibWeb/CSS/MediaQuery.h index 0e2e84e43b1..2035c9a5073 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.h +++ b/Libraries/LibWeb/CSS/MediaQuery.h @@ -127,25 +127,23 @@ public: // Corresponds to `` grammar, with a single comparison static NonnullOwnPtr half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) { - auto feature = adopt_own(*new MediaFeature(Type::Range, id)); - feature->m_range = Range { - .left_value = move(value), - .left_comparison = comparison, - }; - return feature; + return adopt_own(*new MediaFeature(Type::Range, id, + Range { + .left_value = move(value), + .left_comparison = comparison, + })); } // Corresponds to `` grammar, with two comparisons static NonnullOwnPtr range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value) { - auto feature = adopt_own(*new MediaFeature(Type::Range, id)); - feature->m_range = Range { - .left_value = move(left_value), - .left_comparison = left_comparison, - .right_comparison = right_comparison, - .right_value = move(right_value), - }; - return feature; + return adopt_own(*new MediaFeature(Type::Range, id, + Range { + .left_value = move(left_value), + .left_comparison = left_comparison, + .right_comparison = right_comparison, + .right_value = move(right_value), + })); } virtual MatchResult evaluate(HTML::Window const*) const override; @@ -161,15 +159,6 @@ private: Range, }; - MediaFeature(Type type, MediaFeatureID id, Optional value = {}) - : m_type(type) - , m_id(move(id)) - , m_value(move(value)) - { - } - - static bool compare(HTML::Window const& window, MediaFeatureValue left, Comparison comparison, MediaFeatureValue right); - struct Range { MediaFeatureValue left_value; Comparison left_comparison; @@ -177,10 +166,20 @@ private: Optional right_value {}; }; + MediaFeature(Type type, MediaFeatureID id, Variant value = {}) + : m_type(type) + , m_id(move(id)) + , m_value(move(value)) + { + } + + static bool compare(HTML::Window const& window, MediaFeatureValue left, Comparison comparison, MediaFeatureValue right); + MediaFeatureValue const& value() const { return m_value.get(); } + Range const& range() const { return m_value.get(); } + Type m_type; MediaFeatureID m_id; - Optional m_value {}; - Optional m_range {}; + Variant m_value {}; }; class MediaQuery : public RefCounted { From 1e6f703b308cb3e628ccce2f6c09cbad58a76764 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 10:46:09 +0000 Subject: [PATCH 006/141] LibWeb/CSS: Compare MediaFeatureValues using references not copies --- Libraries/LibWeb/CSS/MediaQuery.cpp | 2 +- Libraries/LibWeb/CSS/MediaQuery.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/CSS/MediaQuery.cpp b/Libraries/LibWeb/CSS/MediaQuery.cpp index 87cd73a854a..5e3cbb31eb2 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.cpp +++ b/Libraries/LibWeb/CSS/MediaQuery.cpp @@ -144,7 +144,7 @@ MatchResult MediaFeature::evaluate(HTML::Window const* window) const VERIFY_NOT_REACHED(); } -bool MediaFeature::compare(HTML::Window const& window, MediaFeatureValue left, Comparison comparison, MediaFeatureValue right) +bool MediaFeature::compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right) { if (!left.is_same_type(right)) return false; diff --git a/Libraries/LibWeb/CSS/MediaQuery.h b/Libraries/LibWeb/CSS/MediaQuery.h index 2035c9a5073..3c5edded845 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.h +++ b/Libraries/LibWeb/CSS/MediaQuery.h @@ -173,7 +173,7 @@ private: { } - static bool compare(HTML::Window const& window, MediaFeatureValue left, Comparison comparison, MediaFeatureValue right); + static bool compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right); MediaFeatureValue const& value() const { return m_value.get(); } Range const& range() const { return m_value.get(); } From adfe8a9dcbb638f883863180a6d8dff20a331826 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 11:32:05 +0000 Subject: [PATCH 007/141] LibWeb/CSS: Add functions that report font format/technology support Both `@supports` and `@font-face` need this. There may be some automatic way of querying whether our renderer supports these, but I couldn't figure it out, so here's a basic hard-coded list. I think the font-tech list has false negatives, as I don't know enough about fonts to determine what we support accurately. --- Libraries/LibWeb/CSS/FontFace.cpp | 53 +++++++++++++++++++++++++++++++ Libraries/LibWeb/CSS/FontFace.h | 3 ++ 2 files changed, 56 insertions(+) diff --git a/Libraries/LibWeb/CSS/FontFace.cpp b/Libraries/LibWeb/CSS/FontFace.cpp index a4f1836ce32..bc3bcf74a15 100644 --- a/Libraries/LibWeb/CSS/FontFace.cpp +++ b/Libraries/LibWeb/CSS/FontFace.cpp @@ -398,4 +398,57 @@ GC::Ref FontFace::load() return font_face.loaded(); } +bool font_format_is_supported(FlyString const& name) +{ + // https://drafts.csswg.org/css-fonts-4/#font-format-definitions + // FIXME: Determine this automatically somehow? + if (name.equals_ignoring_ascii_case("collection"sv)) + return false; + if (name.equals_ignoring_ascii_case("embedded-opentype"sv)) + return false; + if (name.equals_ignoring_ascii_case("opentype"sv)) + return true; + if (name.equals_ignoring_ascii_case("svg"sv)) + return false; + if (name.equals_ignoring_ascii_case("truetype"sv)) + return true; + if (name.equals_ignoring_ascii_case("woff"sv)) + return true; + if (name.equals_ignoring_ascii_case("woff2"sv)) + return true; + return false; +} + +bool font_tech_is_supported(FlyString const& name) +{ + // https://drafts.csswg.org/css-fonts-4/#font-tech-definitions + // FIXME: Determine this automatically somehow? + if (name.equals_ignoring_ascii_case("features-opentype"sv)) + return true; + if (name.equals_ignoring_ascii_case("features-aat"sv)) + return false; + if (name.equals_ignoring_ascii_case("features-graphite"sv)) + return false; + if (name.equals_ignoring_ascii_case("variations"sv)) + return true; + if (name.equals_ignoring_ascii_case("color-colrv0"sv)) + return true; + if (name.equals_ignoring_ascii_case("color-colrv1"sv)) + return true; + if (name.equals_ignoring_ascii_case("color-svg"sv)) + return false; + if (name.equals_ignoring_ascii_case("color-sbix"sv)) + return false; + if (name.equals_ignoring_ascii_case("color-cbdt"sv)) + return false; + if (name.equals_ignoring_ascii_case("palettes"sv)) + return false; + if (name.equals_ignoring_ascii_case("incremental"sv)) + return false; + // https://drafts.csswg.org/css-fonts-5/#font-tech-definitions + if (name.equals_ignoring_ascii_case("avar2"sv)) + return false; + return false; +} + } diff --git a/Libraries/LibWeb/CSS/FontFace.h b/Libraries/LibWeb/CSS/FontFace.h index c54a0a986c9..3f8da2edf9d 100644 --- a/Libraries/LibWeb/CSS/FontFace.h +++ b/Libraries/LibWeb/CSS/FontFace.h @@ -113,4 +113,7 @@ private: bool m_is_css_connected { false }; }; +bool font_format_is_supported(FlyString const& name); +bool font_tech_is_supported(FlyString const& name); + } From b6fb4baeb7dca47860b68fabc6e4559d5a1422b8 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 11:36:15 +0000 Subject: [PATCH 008/141] LibWeb/CSS: Implement `@supports font-format()` and `font-tech()` These let authors query whether we support font formats and features. --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 36 +++++++++++- Libraries/LibWeb/CSS/Supports.cpp | 32 +++++++++++ Libraries/LibWeb/CSS/Supports.h | 45 +++++++++++++++ .../css-conditional/at-supports-001-ref.html | 19 +++++++ .../at-supports-font-format-001.html | 56 +++++++++++++++++++ .../at-supports-font-tech-001.html | 56 +++++++++++++++++++ 6 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-conditional/at-supports-001-ref.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-format-001.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-tech-001.html diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index dd095b919c2..5a857b996cc 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -274,10 +275,11 @@ OwnPtr Parser::parse_boolean_expression_group(TokenStream Parser::parse_supports_feature(TokenStream& tokens) { - // = | + // = | + // | | auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& first_token = tokens.consume_a_token(); @@ -307,6 +309,36 @@ OwnPtr Parser::parse_supports_feature(TokenStream = font-tech( )` + if (first_token.is_function("font-tech"sv)) { + TokenStream tech_tokens { first_token.function().value }; + tech_tokens.discard_whitespace(); + auto tech_token = tech_tokens.consume_a_token(); + tech_tokens.discard_whitespace(); + if (tech_tokens.has_next_token() || !tech_token.is(Token::Type::Ident)) + return {}; + + transaction.commit(); + auto tech_name = tech_token.token().ident(); + bool matches = font_tech_is_supported(tech_name); + return Supports::FontTech::create(move(tech_name), matches); + } + + // ` = font-format( )` + if (first_token.is_function("font-format"sv)) { + TokenStream format_tokens { first_token.function().value }; + format_tokens.discard_whitespace(); + auto format_token = format_tokens.consume_a_token(); + format_tokens.discard_whitespace(); + if (format_tokens.has_next_token() || !format_token.is(Token::Type::Ident)) + return {}; + + transaction.commit(); + auto format_name = format_token.token().ident(); + bool matches = font_format_is_supported(format_name); + return Supports::FontFormat::create(move(format_name), matches); + } + return {}; } diff --git a/Libraries/LibWeb/CSS/Supports.cpp b/Libraries/LibWeb/CSS/Supports.cpp index 583074a003f..99112242623 100644 --- a/Libraries/LibWeb/CSS/Supports.cpp +++ b/Libraries/LibWeb/CSS/Supports.cpp @@ -47,6 +47,38 @@ void Supports::Selector::dump(StringBuilder& builder, int indent_levels) const builder.appendff("Selector: `{}` matches={}\n", m_selector, m_matches); } +MatchResult Supports::FontTech::evaluate(HTML::Window const*) const +{ + return as_match_result(m_matches); +} + +String Supports::FontTech::to_string() const +{ + return MUST(String::formatted("font-tech({})", m_tech)); +} + +void Supports::FontTech::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.appendff("FontTech: `{}` matches={}\n", m_tech, m_matches); +} + +MatchResult Supports::FontFormat::evaluate(HTML::Window const*) const +{ + return as_match_result(m_matches); +} + +String Supports::FontFormat::to_string() const +{ + return MUST(String::formatted("font-format({})", m_format)); +} + +void Supports::FontFormat::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.appendff("FontFormat: `{}` matches={}\n", m_format, m_matches); +} + String Supports::to_string() const { return m_condition->to_string(); diff --git a/Libraries/LibWeb/CSS/Supports.h b/Libraries/LibWeb/CSS/Supports.h index 8c9fd403df2..1f19f09e2b9 100644 --- a/Libraries/LibWeb/CSS/Supports.h +++ b/Libraries/LibWeb/CSS/Supports.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -60,6 +61,50 @@ public: bool m_matches; }; + class FontTech final : public BooleanExpression { + public: + static NonnullOwnPtr create(FlyString tech, bool matches) + { + return adopt_own(*new FontTech(move(tech), matches)); + } + virtual ~FontTech() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + + private: + FontTech(FlyString tech, bool matches) + : m_tech(move(tech)) + , m_matches(matches) + { + } + FlyString m_tech; + bool m_matches; + }; + + class FontFormat final : public BooleanExpression { + public: + static NonnullOwnPtr create(FlyString format, bool matches) + { + return adopt_own(*new FontFormat(move(format), matches)); + } + virtual ~FontFormat() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + + private: + FontFormat(FlyString format, bool matches) + : m_format(move(format)) + , m_matches(matches) + { + } + FlyString m_format; + bool m_matches; + }; + static NonnullRefPtr create(NonnullOwnPtr&& condition) { return adopt_ref(*new Supports(move(condition))); diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-conditional/at-supports-001-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-conditional/at-supports-001-ref.html new file mode 100644 index 00000000000..a8157aa7521 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-conditional/at-supports-001-ref.html @@ -0,0 +1,19 @@ + + + + CSS Reftest Reference + + + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-format-001.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-format-001.html new file mode 100644 index 00000000000..1f4f2b49c15 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-format-001.html @@ -0,0 +1,56 @@ + +CSS Conditional Test: @supports font-format() + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-tech-001.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-tech-001.html new file mode 100644 index 00000000000..ac8d857a6b4 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-font-tech-001.html @@ -0,0 +1,56 @@ + +CSS Conditional Test: @supports font-tech() + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+
+
From db597843d62ad0eb500703c535737bd03b4e3997 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 14 Mar 2025 14:37:24 +0000 Subject: [PATCH 009/141] LibWeb/CSS: Correct parsing of `@supports selector()` A couple of fixes here: - Parse a `` instead of a `` - Don't match if any unknown `::-webkit-*` pseudo-elements are found --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 10 +++++++--- Libraries/LibWeb/CSS/Selector.cpp | 18 ++++++++++++++++++ Libraries/LibWeb/CSS/Selector.h | 1 + .../at-supports-selector-001.html | 19 +++++++++++++++++++ .../at-supports-selector-002.html | 18 ++++++++++++++++++ .../at-supports-selector-003.html | 18 ++++++++++++++++++ .../at-supports-selector-004.html | 18 ++++++++++++++++++ Tests/LibWeb/Text/expected/css/supports.txt | 2 +- 8 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-001.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-002.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-003.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-004.html diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 5a857b996cc..441e1de4924 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -304,9 +304,13 @@ OwnPtr Parser::parse_supports_feature(TokenStreamcontains_unknown_webkit_pseudo_element(); + return Supports::Selector::create(builder.to_string_without_validation(), matches); } // ` = font-tech( )` diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index ea887630363..85016c1289e 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -646,6 +646,24 @@ NonnullRefPtr Selector::relative_to(SimpleSelector const& parent) cons return Selector::create(move(copied_compound_selectors)); } +bool Selector::contains_unknown_webkit_pseudo_element() const +{ + for (auto const& compound_selector : m_compound_selectors) { + for (auto const& simple_selector : compound_selector.simple_selectors) { + if (simple_selector.type == SimpleSelector::Type::PseudoClass) { + for (auto const& child_selector : simple_selector.pseudo_class().argument_selector_list) { + if (child_selector->contains_unknown_webkit_pseudo_element()) { + return true; + } + } + } + if (simple_selector.type == SimpleSelector::Type::PseudoElement && simple_selector.pseudo_element().type() == PseudoElement::Type::UnknownWebKit) + return true; + } + } + return false; +} + RefPtr Selector::absolutized(Selector::SimpleSelector const& selector_for_nesting) const { if (!contains_the_nesting_selector()) diff --git a/Libraries/LibWeb/CSS/Selector.h b/Libraries/LibWeb/CSS/Selector.h index 49e218e0a57..f1d06669b48 100644 --- a/Libraries/LibWeb/CSS/Selector.h +++ b/Libraries/LibWeb/CSS/Selector.h @@ -262,6 +262,7 @@ public: NonnullRefPtr 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; } + bool contains_unknown_webkit_pseudo_element() const; RefPtr absolutized(SimpleSelector const& selector_for_nesting) const; u32 specificity() const; String serialize() const; diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-001.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-001.html new file mode 100644 index 00000000000..6bb830972b2 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-001.html @@ -0,0 +1,19 @@ + +CSS Conditional Test: @supports selector() with compound selector + + + + + + +

Test passes if there is a filled green square and no red.

+
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-002.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-002.html new file mode 100644 index 00000000000..97c135d1278 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-002.html @@ -0,0 +1,18 @@ + +CSS Conditional Test: @supports selector() with pseudo-elements. + + + + + +

Test passes if there is a filled green square and no red.

+
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-003.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-003.html new file mode 100644 index 00000000000..0fefd3be139 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-003.html @@ -0,0 +1,18 @@ + +CSS Conditional Test: @supports selector() with -webkit- unknown pseudo-elements and negation. + + + + + +

Test passes if there is a filled green square and no red.

+
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-004.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-004.html new file mode 100644 index 00000000000..d07a88d341d --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-conditional/at-supports-selector-004.html @@ -0,0 +1,18 @@ + +CSS Conditional Test: @supports selector() with multiple selectors doesn't work. + + + + + +

Test passes if there is a filled green square and no red.

+
diff --git a/Tests/LibWeb/Text/expected/css/supports.txt b/Tests/LibWeb/Text/expected/css/supports.txt index a42e2dbfee7..10560b1f716 100644 --- a/Tests/LibWeb/Text/expected/css/supports.txt +++ b/Tests/LibWeb/Text/expected/css/supports.txt @@ -13,5 +13,5 @@ These should all fail: @supports (width: yellow) or (height: green): FAIL @supports (flogwizzle: purple): FAIL @supports selector(.....nope): FAIL -@supports selector(::-webkit-input-placeholder): PASS +@supports selector(::-webkit-input-placeholder): FAIL @supports selector(32) or selector(thing[foo??????bar]): FAIL From 1821896ecf74fea0574307a39bc581cf6add1306 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sat, 15 Mar 2025 15:39:32 +0100 Subject: [PATCH 010/141] LibWeb: Implement the `HTMLLinkElement.sheet` attribute This returns the link element's associated style sheet. --- Libraries/LibWeb/HTML/HTMLLinkElement.cpp | 6 ++++++ Libraries/LibWeb/HTML/HTMLLinkElement.h | 2 ++ Libraries/LibWeb/HTML/HTMLLinkElement.idl | 2 +- .../Text/expected/css/HTMLLinkElement-sheet.txt | 2 ++ .../Text/input/css/HTMLLinkElement-sheet.html | 15 +++++++++++++++ 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/css/HTMLLinkElement-sheet.txt create mode 100644 Tests/LibWeb/Text/input/css/HTMLLinkElement-sheet.html diff --git a/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index 35a6c68a240..381820a5622 100644 --- a/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -138,6 +138,12 @@ String HTMLLinkElement::media() const return attribute(HTML::AttributeNames::media).value_or(String {}); } +// https://drafts.csswg.org/cssom/#dom-linkstyle-sheet +GC::Ptr HTMLLinkElement::sheet() const +{ + return m_loaded_style_sheet; +} + bool HTMLLinkElement::has_loaded_icon() const { return m_relationship & Relationship::Icon && resource() && resource()->is_loaded() && resource()->has_encoded_data(); diff --git a/Libraries/LibWeb/HTML/HTMLLinkElement.h b/Libraries/LibWeb/HTML/HTMLLinkElement.h index ca13885a51a..4adb7fac333 100644 --- a/Libraries/LibWeb/HTML/HTMLLinkElement.h +++ b/Libraries/LibWeb/HTML/HTMLLinkElement.h @@ -48,6 +48,8 @@ public: void set_media(String); String media() const; + GC::Ptr sheet() const; + private: HTMLLinkElement(DOM::Document&, DOM::QualifiedName); diff --git a/Libraries/LibWeb/HTML/HTMLLinkElement.idl b/Libraries/LibWeb/HTML/HTMLLinkElement.idl index 71d12a6af6e..27ae37a1118 100644 --- a/Libraries/LibWeb/HTML/HTMLLinkElement.idl +++ b/Libraries/LibWeb/HTML/HTMLLinkElement.idl @@ -60,4 +60,4 @@ interface HTMLLinkElement : HTMLElement { [CEReactions, Reflect] attribute DOMString target; }; -// FIXME: HTMLLinkElement includes LinkStyle; +HTMLLinkElement includes LinkStyle; diff --git a/Tests/LibWeb/Text/expected/css/HTMLLinkElement-sheet.txt b/Tests/LibWeb/Text/expected/css/HTMLLinkElement-sheet.txt new file mode 100644 index 00000000000..b411111934f --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/HTMLLinkElement-sheet.txt @@ -0,0 +1,2 @@ +sheet property initial value: null +Sheet property after stylesheet loaded: [object CSSStyleSheet] diff --git a/Tests/LibWeb/Text/input/css/HTMLLinkElement-sheet.html b/Tests/LibWeb/Text/input/css/HTMLLinkElement-sheet.html new file mode 100644 index 00000000000..7c0902f8fb2 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/HTMLLinkElement-sheet.html @@ -0,0 +1,15 @@ + + + From a6935299eba70555ec1a948fdc1e7838ff5c5157 Mon Sep 17 00:00:00 2001 From: stasoid Date: Thu, 6 Mar 2025 18:10:38 +0500 Subject: [PATCH 011/141] LibWeb: Correctly calculate static position rect when absolutely positioned element is a descendant of inline-block Sets inline block offsets in InlineFormattingContext.cpp, but this is not enough. When static position rect is calculated during layout, not all ancestors of abspos box may have their offsets calculated yet (more info here: https://github.com/LadybirdBrowser/ladybird/pull/2583#issuecomment-2507140272). So now static position rect is calculated relative to static containing block during layout and calculation relative to actual containing block is done later in FormattingContext::layout_absolutely_positioned_element. Fixes wpt/css/CSS2/abspos/static-inside-inline-block.html --- Libraries/LibWeb/Layout/BlockFormattingContext.cpp | 3 +-- Libraries/LibWeb/Layout/FlexFormattingContext.cpp | 5 +---- Libraries/LibWeb/Layout/FormattingContext.cpp | 3 ++- Libraries/LibWeb/Layout/GridFormattingContext.cpp | 3 +-- Libraries/LibWeb/Layout/InlineFormattingContext.cpp | 13 +++++++++++-- Libraries/LibWeb/Layout/LayoutState.h | 1 + Libraries/LibWeb/Layout/Node.cpp | 1 + Libraries/LibWeb/Layout/TableFormattingContext.cpp | 5 ++--- ...lockify-layout-internal-box-without-crashing.txt | 4 ++-- .../CSS2/abspos/static-inside-inline-block-ref.html | 11 +++++++++++ .../css/CSS2/abspos/static-inside-inline-block.html | 12 ++++++++++++ 11 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/abspos/static-inside-inline-block-ref.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/CSS2/abspos/static-inside-inline-block.html diff --git a/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index 4d1424a423c..a11d60753b4 100644 --- a/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -672,8 +672,7 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain if (box.is_absolutely_positioned()) { StaticPositionRect static_position; - auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block()); - static_position.rect = { offset_to_static_parent.location().translated(0, m_y_offset_of_current_block_container.value()), { 0, 0 } }; + static_position.rect = { { 0, m_y_offset_of_current_block_container.value() }, { 0, 0 } }; box_state.set_static_position_rect(static_position); return; } diff --git a/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index ae8a73cba60..28a72296edd 100644 --- a/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -2253,14 +2253,11 @@ StaticPositionRect FlexFormattingContext::calculate_static_position_rect(Box con break; } - auto absolute_position_of_flex_container = absolute_content_rect(flex_container()).location(); - auto absolute_position_of_abspos_containing_block = absolute_content_rect(*box.containing_block()).location(); - auto flex_container_width = is_row_layout() ? inner_main_size(m_flex_container_state) : inner_cross_size(m_flex_container_state); auto flex_container_height = is_row_layout() ? inner_cross_size(m_flex_container_state) : inner_main_size(m_flex_container_state); StaticPositionRect static_position_rect; - static_position_rect.rect = { absolute_position_of_flex_container - absolute_position_of_abspos_containing_block, { flex_container_width, flex_container_height } }; + static_position_rect.rect = { { 0, 0 }, { flex_container_width, flex_container_height } }; static_position_rect.horizontal_alignment = is_row_layout() ? main_axis_alignment : cross_axis_alignment; static_position_rect.vertical_alignment = is_row_layout() ? cross_axis_alignment : main_axis_alignment; return static_position_rect; diff --git a/Libraries/LibWeb/Layout/FormattingContext.cpp b/Libraries/LibWeb/Layout/FormattingContext.cpp index 37bdf7d584d..6acb33d1c6f 100644 --- a/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -1179,7 +1179,6 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el box_state.margin_bottom = margin_bottom.to_px(box, width_of_containing_block); } -// NOTE: This is different from content_box_rect_in_ancestor_coordinate_space() as this does *not* follow the containing block chain up, but rather the parent() chain. CSSPixelRect FormattingContext::content_box_rect_in_static_position_ancestor_coordinate_space(Box const& box, Box const& ancestor_box) const { auto rect = content_box_rect(box); @@ -1261,6 +1260,8 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava CSSPixelPoint used_offset; auto static_position = m_state.get(box).static_position(); + auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block()); + static_position += offset_to_static_parent.location(); if (box.computed_values().inset().top().is_auto() && box.computed_values().inset().bottom().is_auto()) { used_offset.set_y(static_position.y()); diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 2768d4832c5..94ba44d5177 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -2632,8 +2632,7 @@ StaticPositionRect GridFormattingContext::calculate_static_position_rect(Box con // layout_absolutely_positioned_element() defined for GFC knows how to handle this case. StaticPositionRect static_position; auto const& box_state = m_state.get(box); - auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block()); - static_position.rect = { offset_to_static_parent.location().translated(0, 0), { box_state.content_width(), box_state.content_height() } }; + static_position.rect = { { 0, 0 }, { box_state.content_width(), box_state.content_height() } }; return static_position; } } diff --git a/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 28f2b8fe732..50469a5d922 100644 --- a/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -411,6 +411,16 @@ void InlineFormattingContext::generate_line_boxes() } } + for (auto& line_box : line_boxes) { + for (auto& fragment : line_box.fragments()) { + if (fragment.layout_node().is_inline_block()) { + auto& box = as(fragment.layout_node()); + auto& box_state = m_state.get_mutable(box); + box_state.set_content_offset(fragment.offset()); + } + } + } + for (auto* box : absolute_boxes) { auto& box_state = m_state.get_mutable(*box); box_state.set_static_position_rect(calculate_static_position_rect(*box)); @@ -494,9 +504,8 @@ StaticPositionRect InlineFormattingContext::calculate_static_position_rect(Box c } else { // Easy case: no previous sibling, we're at the top of the containing block. } - auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block()); StaticPositionRect static_position_rect; - static_position_rect.rect = { offset_to_static_parent.location().translated(x, y), { 0, 0 } }; + static_position_rect.rect = { { x, y }, { 0, 0 } }; return static_position_rect; } diff --git a/Libraries/LibWeb/Layout/LayoutState.h b/Libraries/LibWeb/Layout/LayoutState.h index dd913fbd3bf..e39ab1b2b05 100644 --- a/Libraries/LibWeb/Layout/LayoutState.h +++ b/Libraries/LibWeb/Layout/LayoutState.h @@ -90,6 +90,7 @@ struct LayoutState { void set_content_x(CSSPixels x) { offset.set_x(x); } void set_content_y(CSSPixels y) { offset.set_y(y); } + // offset from top-left corner of content area of box's containing block to top-left corner of box's content area CSSPixelPoint offset; SizeConstraint width_constraint { SizeConstraint::None }; diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index 7d98c5853b8..275fcb37d5a 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -140,6 +140,7 @@ Box const* Node::containing_block() const return nearest_ancestor_capable_of_forming_a_containing_block(*this); } +// returns containing block this node would have had if its position was static Box const* Node::static_position_containing_block() const { return nearest_ancestor_capable_of_forming_a_containing_block(*this); diff --git a/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Libraries/LibWeb/Layout/TableFormattingContext.cpp index 43611e759cc..c9e80f33522 100644 --- a/Libraries/LibWeb/Layout/TableFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -1853,12 +1853,11 @@ CSSPixels TableFormattingContext::border_spacing_vertical() const return computed_values.border_spacing_vertical().to_px(table_box()); } -StaticPositionRect TableFormattingContext::calculate_static_position_rect(Box const& box) const +StaticPositionRect TableFormattingContext::calculate_static_position_rect(Box const&) const { // FIXME: Implement static position calculation for table descendants instead of always returning a rectangle with zero position and size. StaticPositionRect static_position; - auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block()); - static_position.rect = { offset_to_static_parent.location(), { 0, 0 } }; + static_position.rect = { { 0, 0 }, { 0, 0 } }; return static_position; } diff --git a/Tests/LibWeb/Layout/expected/blockify-layout-internal-box-without-crashing.txt b/Tests/LibWeb/Layout/expected/blockify-layout-internal-box-without-crashing.txt index 6ee93b215d0..e0c6559f28e 100644 --- a/Tests/LibWeb/Layout/expected/blockify-layout-internal-box-without-crashing.txt +++ b/Tests/LibWeb/Layout/expected/blockify-layout-internal-box-without-crashing.txt @@ -6,7 +6,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline Box at (10,10) content-size 0x0 table-row-group children: not-inline Box at (10,10) content-size 0x0 table-row children: not-inline BlockContainer <(anonymous)> at (10,10) content-size 0x0 table-cell [BFC] children: not-inline - BlockContainer at (9,9) content-size 0x0 positioned [BFC] children: not-inline + BlockContainer at (11,11) content-size 0x0 positioned [BFC] children: not-inline ViewportPaintable (Viewport<#document>) [0,0 800x600] PaintableWithLines (BlockContainer) [0,0 800x20] @@ -16,4 +16,4 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600] PaintableBox (Box) [10,10 0x0] PaintableBox (Box) [10,10 0x0] PaintableWithLines (BlockContainer(anonymous)) [10,10 0x0] - PaintableWithLines (BlockContainer) [8,8 2x2] + PaintableWithLines (BlockContainer) [10,10 2x2] diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/abspos/static-inside-inline-block-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/abspos/static-inside-inline-block-ref.html new file mode 100644 index 00000000000..e76a2ccb972 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/abspos/static-inside-inline-block-ref.html @@ -0,0 +1,11 @@ + +Static position inside inline-block + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/abspos/static-inside-inline-block.html b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/abspos/static-inside-inline-block.html new file mode 100644 index 00000000000..f646ffff70f --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/abspos/static-inside-inline-block.html @@ -0,0 +1,12 @@ + +Static position inside inline-block + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
From c85df78c4c3ede6c5a64e3f1ed18e060e07180ff Mon Sep 17 00:00:00 2001 From: mikiubo Date: Wed, 12 Mar 2025 13:53:44 +0100 Subject: [PATCH 012/141] LibRegex: Remove orphaned save points in nested LookAhead --- Libraries/LibRegex/RegexByteCode.cpp | 9 +++++++++ Libraries/LibRegex/RegexByteCode.h | 15 +++++++++++++++ Tests/LibRegex/Regex.cpp | 3 +++ 3 files changed, 27 insertions(+) diff --git a/Libraries/LibRegex/RegexByteCode.cpp b/Libraries/LibRegex/RegexByteCode.cpp index 5447b4f0edf..b2df209a7be 100644 --- a/Libraries/LibRegex/RegexByteCode.cpp +++ b/Libraries/LibRegex/RegexByteCode.cpp @@ -218,6 +218,15 @@ ALWAYS_INLINE ExecutionResult OpCode_FailForks::execute(MatchInput const& input, return ExecutionResult::Failed_ExecuteLowPrioForks; } +ALWAYS_INLINE ExecutionResult OpCode_PopSaved::execute(MatchInput const& input, MatchState&) const +{ + if (input.saved_positions.is_empty() || input.saved_code_unit_positions.is_empty()) + return ExecutionResult::Failed_ExecuteLowPrioForks; + input.saved_positions.take_last(); + input.saved_code_unit_positions.take_last(); + return ExecutionResult::Failed_ExecuteLowPrioForks; +} + ALWAYS_INLINE ExecutionResult OpCode_Jump::execute(MatchInput const&, MatchState& state) const { state.instruction_position += offset(); diff --git a/Libraries/LibRegex/RegexByteCode.h b/Libraries/LibRegex/RegexByteCode.h index 5fede59fd65..b9a8cdb57b2 100644 --- a/Libraries/LibRegex/RegexByteCode.h +++ b/Libraries/LibRegex/RegexByteCode.h @@ -31,6 +31,7 @@ using ByteCodeValueType = u64; __ENUMERATE_OPCODE(ForkReplaceJump) \ __ENUMERATE_OPCODE(ForkReplaceStay) \ __ENUMERATE_OPCODE(FailForks) \ + __ENUMERATE_OPCODE(PopSaved) \ __ENUMERATE_OPCODE(SaveLeftCaptureGroup) \ __ENUMERATE_OPCODE(SaveRightCaptureGroup) \ __ENUMERATE_OPCODE(SaveRightNamedCaptureGroup) \ @@ -266,9 +267,15 @@ public: switch (type) { case LookAroundType::LookAhead: { // SAVE + // FORKJUMP _BODY + // POPSAVED + // LABEL _BODY // REGEXP BODY // RESTORE empend((ByteCodeValueType)OpCodeId::Save); + empend((ByteCodeValueType)OpCodeId::ForkJump); + empend((ByteCodeValueType)1); + empend((ByteCodeValueType)OpCodeId::PopSaved); extend(move(lookaround_body)); empend((ByteCodeValueType)OpCodeId::Restore); return; @@ -613,6 +620,14 @@ public: ByteString arguments_string() const override { return ByteString::empty(); } }; +class OpCode_PopSaved final : public OpCode { +public: + ExecutionResult execute(MatchInput const& input, MatchState& state) const override; + ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::PopSaved; } + ALWAYS_INLINE size_t size() const override { return 1; } + ByteString arguments_string() const override { return ByteString::empty(); } +}; + class OpCode_Save final : public OpCode { public: ExecutionResult execute(MatchInput const& input, MatchState& state) const override; diff --git a/Tests/LibRegex/Regex.cpp b/Tests/LibRegex/Regex.cpp index 5d9e5045d3f..2c8e393d96f 100644 --- a/Tests/LibRegex/Regex.cpp +++ b/Tests/LibRegex/Regex.cpp @@ -738,6 +738,9 @@ TEST_CASE(ECMA262_match) // Optimizer bug, ignoring an enabled trailing 'invert' when comparing blocks, ladybird#3421. { "[^]*[^]"sv, "i"sv, true }, { "xx|...|...."sv, "cd"sv, false }, + // Tests nested lookahead with alternation - verifies proper save/restore stack cleanup + { "a(?=.(?=c)|b)b"sv, "ab"sv, true }, + { "(?=)(?=\\d)"sv, "smart"sv, false }, }; for (auto& test : tests) { From b8adf193f52c4c7089e4e97edd380db189bd6362 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 16 Mar 2025 20:56:04 +1300 Subject: [PATCH 013/141] LibWeb/SVG: Work around no layout node in SVGCircleElement::get_path This is clearly not the correct fix, but instead of crashing let's log an error and bail. Works around a crash seen on both: * https://reddit.com * https://www.w3.org/TR/web-animations-1 (#879) --- Libraries/LibWeb/SVG/SVGCircleElement.cpp | 5 +++++ .../Layout/svg-circle-element-crash-no-layout-node.html | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 Tests/LibWeb/Crash/Layout/svg-circle-element-crash-no-layout-node.html diff --git a/Libraries/LibWeb/SVG/SVGCircleElement.cpp b/Libraries/LibWeb/SVG/SVGCircleElement.cpp index 381a0a47468..ee85ae3500f 100644 --- a/Libraries/LibWeb/SVG/SVGCircleElement.cpp +++ b/Libraries/LibWeb/SVG/SVGCircleElement.cpp @@ -61,6 +61,11 @@ void SVGCircleElement::apply_presentational_hints(GC::Refcomputed_values().cx().to_px(*node, viewport_size.width())); auto cy = float(node->computed_values().cy().to_px(*node, viewport_size.height())); // Percentages refer to the normalized diagonal of the current SVG viewport diff --git a/Tests/LibWeb/Crash/Layout/svg-circle-element-crash-no-layout-node.html b/Tests/LibWeb/Crash/Layout/svg-circle-element-crash-no-layout-node.html new file mode 100644 index 00000000000..e15d74a66e9 --- /dev/null +++ b/Tests/LibWeb/Crash/Layout/svg-circle-element-crash-no-layout-node.html @@ -0,0 +1,6 @@ + + + + + + From 20890d7b70200ae4a8170d8537f2729dc124df3a Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 17 Mar 2025 20:16:42 -0600 Subject: [PATCH 014/141] Meta: Add fontconfig and freetype vcpkg overlays Temporary until their canonical git forge is back up and running --- Meta/CMake/all_the_debug_macros.cmake | 3 + .../overlay-ports/fontconfig/emscripten.diff | 13 ++ .../fix-wasm-shared-memory-atomics.patch | 14 ++ .../overlay-ports/fontconfig/libgetopt.patch | 130 ++++++++++++++++++ .../fontconfig/no-etc-symlinks.patch | 19 +++ .../overlay-ports/fontconfig/portfile.cmake | 109 +++++++++++++++ .../vcpkg/overlay-ports/fontconfig/usage | 9 ++ .../fontconfig/vcpkg-cmake-wrapper.cmake.in | 51 +++++++ .../vcpkg/overlay-ports/fontconfig/vcpkg.json | 60 ++++++++ .../overlay-ports/freetype/0003-Fix-UWP.patch | 65 +++++++++ .../freetype/brotli-static.patch | 21 +++ .../vcpkg/overlay-ports/freetype/bzip2.patch | 13 ++ .../overlay-ports/freetype/fix-exports.patch | 40 ++++++ .../overlay-ports/freetype/portfile.cmake | 98 +++++++++++++ .../freetype/subpixel-rendering.patch | 13 ++ Meta/CMake/vcpkg/overlay-ports/freetype/usage | 4 + .../freetype/vcpkg-cmake-wrapper.cmake | 95 +++++++++++++ .../vcpkg/overlay-ports/freetype/vcpkg.json | 55 ++++++++ 18 files changed, 812 insertions(+) create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/emscripten.diff create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/fix-wasm-shared-memory-atomics.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/libgetopt.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/no-etc-symlinks.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/portfile.cmake create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/usage create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg-cmake-wrapper.cmake.in create mode 100644 Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg.json create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/0003-Fix-UWP.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/brotli-static.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/bzip2.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/fix-exports.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/portfile.cmake create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/subpixel-rendering.patch create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/usage create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg-cmake-wrapper.cmake create mode 100644 Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg.json diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 54ed3e976c5..8f84f323777 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -88,3 +88,6 @@ set(XML_PARSER_DEBUG ON) # set(gn_include_dirs_DEBUG ON) # set(gn_ldflags_DEBUG ON) # set(gn_lib_dirs_DEBUG ON) +# Third-party: fontconfig overlay +# set(Fontconfig_LIBRARY_DEBUG ON) +# set(UUID_LIBRARY_DEBUG ON) diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/emscripten.diff b/Meta/CMake/vcpkg/overlay-ports/fontconfig/emscripten.diff new file mode 100644 index 00000000000..da8ef2923d8 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/emscripten.diff @@ -0,0 +1,13 @@ +diff --git a/meson.build b/meson.build +index 08d9532..37cc195 100644 +--- a/meson.build ++++ b/meson.build +@@ -289,7 +289,7 @@ if fc_cachedir in ['yes', 'no', 'default'] + endif + endif + +-if host_machine.system() != 'windows' ++if host_machine.system() != 'windows' and host_machine.system() != 'emscripten' + thread_dep = dependency('threads') + conf.set('HAVE_PTHREAD', 1) + deps += [thread_dep] diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/fix-wasm-shared-memory-atomics.patch b/Meta/CMake/vcpkg/overlay-ports/fontconfig/fix-wasm-shared-memory-atomics.patch new file mode 100644 index 00000000000..d94d109c800 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/fix-wasm-shared-memory-atomics.patch @@ -0,0 +1,14 @@ +diff --git a/meson.build b/meson.build +index 8e78700..95bae59 100644 +--- a/meson.build ++++ b/meson.build +@@ -112,6 +112,9 @@ check_alignofs = [ + ] + + add_project_arguments('-DHAVE_CONFIG_H', language: 'c') ++if cc.get_id() == 'clang' and host_machine.cpu_family() == 'wasm' ++ add_project_arguments('-matomics', '-mbulk-memory', language: 'c') ++endif + + c_args = [] + diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/libgetopt.patch b/Meta/CMake/vcpkg/overlay-ports/fontconfig/libgetopt.patch new file mode 100644 index 00000000000..db430165e9b --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/libgetopt.patch @@ -0,0 +1,130 @@ +diff --git a/fc-cache/meson.build b/fc-cache/meson.build +index 5e40fac..3c3e46b 100644 +--- a/fc-cache/meson.build ++++ b/fc-cache/meson.build +@@ -1,6 +1,7 @@ + fccache = executable('fc-cache', ['fc-cache.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], ++ dependencies: [getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-cat/meson.build b/fc-cat/meson.build +index f26e4b8..476c0f9 100644 +--- a/fc-cat/meson.build ++++ b/fc-cat/meson.build +@@ -1,6 +1,7 @@ + fccat = executable('fc-cat', ['fc-cat.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], ++ dependencies: [getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-conflist/meson.build b/fc-conflist/meson.build +index f543cf9..f06640b 100644 +--- a/fc-conflist/meson.build ++++ b/fc-conflist/meson.build +@@ -1,6 +1,7 @@ + fcconflist = executable('fc-conflist', ['fc-conflist.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], ++ dependencies: [getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-list/meson.build b/fc-list/meson.build +index 2f679d5..4b0fb62 100644 +--- a/fc-list/meson.build ++++ b/fc-list/meson.build +@@ -1,6 +1,7 @@ + fclist = executable('fc-list', ['fc-list.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], ++ dependencies: [getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-match/meson.build b/fc-match/meson.build +index aca8bc8..cab4f09 100644 +--- a/fc-match/meson.build ++++ b/fc-match/meson.build +@@ -1,6 +1,7 @@ + fcmatch = executable('fc-match', ['fc-match.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], ++ dependencies: [getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-pattern/meson.build b/fc-pattern/meson.build +index 07de245..b957c67 100644 +--- a/fc-pattern/meson.build ++++ b/fc-pattern/meson.build +@@ -1,6 +1,7 @@ + fcpattern = executable('fc-pattern', ['fc-pattern.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], ++ dependencies: [getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-query/meson.build b/fc-query/meson.build +index d0f2dd4..940b021 100644 +--- a/fc-query/meson.build ++++ b/fc-query/meson.build +@@ -1,7 +1,7 @@ + fcquery = executable('fc-query', ['fc-query.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], +- dependencies: [freetype_dep], ++ dependencies: [freetype_dep, getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-scan/meson.build b/fc-scan/meson.build +index 4de2134..c5b2b67 100644 +--- a/fc-scan/meson.build ++++ b/fc-scan/meson.build +@@ -1,7 +1,7 @@ + fcscan = executable('fc-scan', ['fc-scan.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], +- dependencies: [freetype_dep], ++ dependencies: [freetype_dep, getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/fc-validate/meson.build b/fc-validate/meson.build +index e2b956e..8902d59 100644 +--- a/fc-validate/meson.build ++++ b/fc-validate/meson.build +@@ -1,7 +1,7 @@ + fcvalidate = executable('fc-validate', ['fc-validate.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], +- dependencies: [freetype_dep], ++ dependencies: [freetype_dep, getopt_dep], + c_args: c_args, + install: true, + ) +diff --git a/meson.build b/meson.build +index f616600..6d82a16 100644 +--- a/meson.build ++++ b/meson.build +@@ -202,6 +202,14 @@ if cc.links(files('meson-cc-tests/solaris-atomic-operations.c'), name: 'Solaris + conf.set('HAVE_SOLARIS_ATOMIC_OPS', 1) + endif + ++if host_machine.system() == 'windows' ++ conf.set('HAVE_GETOPT', 1) ++ conf.set('HAVE_GETOPT_LONG', 1) ++ getopt_dep = cc.find_library('getopt', required: false) ++else ++ getopt_dep = dependency('', required: false) ++endif ++ + + # Check iconv support + iconv_dep = [] diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/no-etc-symlinks.patch b/Meta/CMake/vcpkg/overlay-ports/fontconfig/no-etc-symlinks.patch new file mode 100644 index 00000000000..774640ca0d3 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/no-etc-symlinks.patch @@ -0,0 +1,19 @@ +--- a/conf.d/link_confs.py 2022-03-24 04:13:59.000982000 +0900 ++++ b/conf.d/link_confs.py 2022-03-24 04:14:46.271964000 +0900 +@@ -4,6 +4,7 @@ + import sys + import argparse + import platform ++import shutil + from pathlib import PurePath + + if __name__=='__main__': +@@ -32,7 +33,7 @@ + except FileNotFoundError: + pass + try: +- os.symlink(os.path.relpath(src, start=args.confpath), dst) ++ shutil.copyfile(src, dst) + except NotImplementedError: + # Not supported on this version of Windows + break diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/portfile.cmake b/Meta/CMake/vcpkg/overlay-ports/fontconfig/portfile.cmake new file mode 100644 index 00000000000..461c1a5c1ee --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/portfile.cmake @@ -0,0 +1,109 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO freedesktop-sdk/mirrors/freedesktop/fontconfig/fontconfig + REF ${VERSION} + SHA512 daa6d1e6058e12c694d9e1512e09be957ff7f3fa375246b9d13eb0a8cf2f21e1512a5cabe93f270e96790e2c20420bf7422d213e43ab9749da3255286ea65a7c + HEAD_REF master + PATCHES + emscripten.diff + no-etc-symlinks.patch + libgetopt.patch + fix-wasm-shared-memory-atomics.patch +) + +set(options "") +if("nls" IN_LIST FEATURES) + list(APPEND options "-Dnls=enabled") +else() + list(APPEND options "-Dnls=disabled") +endif() +if("tools" IN_LIST FEATURES) + list(APPEND options "-Dtools=enabled") +else() + list(APPEND options "-Dtools=disabled") +endif() + +vcpkg_configure_meson( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${options} + -Ddoc=disabled + -Dcache-build=disabled + -Diconv=enabled + -Dtests=disabled + ADDITIONAL_BINARIES + "gperf = ['${CURRENT_HOST_INSTALLED_DIR}/tools/gperf/gperf${VCPKG_HOST_EXECUTABLE_SUFFIX}']" +) + +# https://www.freedesktop.org/software/fontconfig/fontconfig-user.html +# Adding OPTIONS for e.g. baseconfig-dir etc. won't work since meson will try to install into those dirs! +# Since adding OPTIONS does not work use a replacement in the generated config.h instead +set(replacement "") +if(VCPKG_TARGET_IS_WINDOWS) + set(replacement "**invalid-fontconfig-dir-do-not-use**") +endif() +set(configfile "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/config.h") +vcpkg_replace_string("${configfile}" "${CURRENT_PACKAGES_DIR}" "${replacement}") +vcpkg_replace_string("${configfile}" "#define FC_TEMPLATEDIR \"/share/fontconfig/conf.avail\"" "#define FC_TEMPLATEDIR \"/usr/share/fontconfig/conf.avail\"" IGNORE_UNCHANGED) +if(NOT VCPKG_BUILD_TYPE) + set(configfile "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/config.h") + vcpkg_replace_string("${configfile}" "${CURRENT_PACKAGES_DIR}/debug" "${replacement}") + vcpkg_replace_string("${configfile}" "#define FC_TEMPLATEDIR \"/share/fontconfig/conf.avail\"" "#define FC_TEMPLATEDIR \"/usr/share/fontconfig/conf.avail\"" IGNORE_UNCHANGED) +endif() + +vcpkg_install_meson(ADD_BIN_TO_PATH) + +vcpkg_copy_pdbs() +#Fix missing libintl static dependency +if("nls" IN_LIST FEATURES AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT VCPKG_BUILD_TYPE) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/fontconfig.pc" "-liconv" "-liconv -lintl" IGNORE_UNCHANGED) + endif() + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/fontconfig.pc" "-liconv" "-liconv -lintl" IGNORE_UNCHANGED) +endif() +vcpkg_fixup_pkgconfig() + +# Fix paths in debug pc file. +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/fontconfig.pc") +if(EXISTS "${_file}") + file(READ "${_file}" _contents) + string(REPLACE "/etc" "/../etc" _contents "${_contents}") + string(REPLACE "/var" "/../var" _contents "${_contents}") + file(WRITE "${_file}" "${_contents}") +endif() + +# Make path to cache in fonts.conf relative +set(_file "${CURRENT_PACKAGES_DIR}/etc/fonts/fonts.conf") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "${CURRENT_PACKAGES_DIR}/var/cache/fontconfig" "./../../var/cache/fontconfig" IGNORE_UNCHANGED) +endif() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/var" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/debug/etc") + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + if(VCPKG_TARGET_IS_WINDOWS) + set(DEFINE_FC_PUBLIC "#define FcPublic __declspec(dllimport)") + else() + set(DEFINE_FC_PUBLIC "#define FcPublic __attribute__((visibility(\"default\")))") + endif() + foreach(HEADER IN ITEMS fcfreetype.h fontconfig.h) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/fontconfig/${HEADER}" + "#define FcPublic" + "${DEFINE_FC_PUBLIC}" + ) + endforeach() +endif() + +if("tools" IN_LIST FEATURES) + vcpkg_copy_tools( + TOOL_NAMES fc-match fc-cat fc-list fc-pattern fc-query fc-scan fc-cache fc-validate fc-conflist + AUTO_CLEAN + ) +endif() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING") diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/usage b/Meta/CMake/vcpkg/overlay-ports/fontconfig/usage new file mode 100644 index 00000000000..b59bc482f7a --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/usage @@ -0,0 +1,9 @@ +fontconfig is compatible with built-in CMake targets: + + find_package(Fontconfig REQUIRED) # since CMake 3.14 + target_link_libraries(main PRIVATE Fontconfig::Fontconfig) + +fontconfig provides pkg-config modules: + + # Font configuration and customization library + fontconfig diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg-cmake-wrapper.cmake.in b/Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 00000000000..63a1f01a744 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,51 @@ +_find_package(${ARGS}) +if(Fontconfig_FOUND) # theoretically this could be skipped. If the wrapper is installed it should be found! + if(NOT TARGET Fontconfig::Fontconfig) + # Simplify wrapper for case of vendored FindFontconfig.cmake + add_library(Fontconfig::Fontconfig UNKNOWN IMPORTED) + endif() + include(SelectLibraryConfigurations) + find_library(Fontconfig_LIBRARY_DEBUG NAMES fontconfig fontconfigd NAMES_PER_DIR PATH_SUFFIXES lib PATHS "${_INSTALLED_DIR}/debug" NO_DEFAULT_PATH) + find_library(Fontconfig_LIBRARY_RELEASE NAMES fontconfig NAMES_PER_DIR PATH_SUFFIXES lib PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}" NO_DEFAULT_PATH) + select_library_configurations(Fontconfig) + set_target_properties(Fontconfig::Fontconfig PROPERTIES + IMPORTED_CONFIGURATIONS "Release" + IMPORTED_LOCATION_RELEASE "${Fontconfig_LIBRARY_RELEASE}" + ) + if(Fontconfig_LIBRARY_DEBUG) + set_property(TARGET Fontconfig::Fontconfig APPEND PROPERTY IMPORTED_CONFIGURATIONS "Debug") + set_target_properties(Fontconfig::Fontconfig PROPERTIES IMPORTED_LOCATION_DEBUG "${Fontconfig_LIBRARY_DEBUG}") + endif() + find_package(Freetype) + if(Freetype_FOUND) + list(APPEND Fontconfig_LIBRARIES "${FREETYPE_LIBRARIES}") + if(TARGET Freetype::Freetype) + set_property(TARGET Fontconfig::Fontconfig APPEND PROPERTY INTERFACE_LINK_LIBRARIES "\$") + else() + # TODO link FREETYPE_LIBRARIES transformed for $. + endif() + endif() + find_package(EXPAT) + if(EXPAT_FOUND) + list(APPEND Fontconfig_LIBRARIES "${EXPAT_LIBRARIES}") + if(TARGET EXPAT::EXPAT) + set_property(TARGET Fontconfig::Fontconfig APPEND PROPERTY INTERFACE_LINK_LIBRARIES "\$") + else() + # TODO link EXPAT_LIBRARIES transformed for $. + endif() + endif() + if("@VCPKG_TARGET_IS_LINUX@") + find_library(UUID_LIBRARY_DEBUG NAMES uuid uuidd uuid_d NAMES_PER_DIR PATH_SUFFIXES lib PATHS "${_INSTALLED_DIR}/debug" NO_DEFAULT_PATH) + find_library(UUID_LIBRARY_RELEASE NAMES uuid NAMES_PER_DIR PATH_SUFFIXES lib PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}" NO_DEFAULT_PATH) + select_library_configurations(UUID) + if(UUID_LIBRARIES) + list(APPEND Fontconfig_LIBRARIES "${UUID_LIBRARIES}") + if(UUID_LIBRARY_DEBUG) + set_property(TARGET Fontconfig::Fontconfig APPEND PROPERTY INTERFACE_LINK_LIBRARIES "$<$>:${UUID_LIBRARY_RELEASE}>") + set_property(TARGET Fontconfig::Fontconfig APPEND PROPERTY INTERFACE_LINK_LIBRARIES "$<$:${UUID_LIBRARY_DEBUG}>") + else() + set_property(TARGET Fontconfig::Fontconfig APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${UUID_LIBRARY_RELEASE}") + endif() + endif() + endif() +endif() diff --git a/Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg.json b/Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg.json new file mode 100644 index 00000000000..18fe0b72b80 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/fontconfig/vcpkg.json @@ -0,0 +1,60 @@ +{ + "name": "fontconfig", + "version": "2.15.0", + "port-version": 2, + "description": "Library for configuring and customizing font access.", + "homepage": "https://www.freedesktop.org/wiki/Software/fontconfig", + "license": "MIT", + "supports": "!uwp", + "dependencies": [ + "dirent", + "expat", + { + "name": "freetype", + "default-features": false + }, + { + "name": "gperf", + "host": true + }, + { + "name": "libiconv", + "platform": "!windows" + }, + { + "name": "libuuid", + "platform": "!osx & !windows" + }, + { + "name": "pthread", + "platform": "!emscripten & !windows" + }, + { + "name": "vcpkg-tool-meson", + "host": true + } + ], + "features": { + "nls": { + "description": "Native languages support", + "dependencies": [ + { + "name": "gettext", + "host": true, + "default-features": false, + "features": [ + "tools" + ] + }, + "gettext-libintl" + ] + }, + "tools": { + "description": "Build tools", + "supports": "!emscripten", + "dependencies": [ + "getopt" + ] + } + } +} diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/0003-Fix-UWP.patch b/Meta/CMake/vcpkg/overlay-ports/freetype/0003-Fix-UWP.patch new file mode 100644 index 00000000000..b3a04188c64 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/0003-Fix-UWP.patch @@ -0,0 +1,65 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index db48e9f..5c35276 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -328,6 +328,10 @@ else () + list(APPEND BASE_SRCS src/base/ftdebug.c) + endif () + ++if(MSVC) ++ add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS) ++endif() ++ + if (BUILD_FRAMEWORK) + list(APPEND BASE_SRCS builds/mac/freetype-Info.plist) + endif () +diff --git a/include/freetype/freetype.h b/include/freetype/freetype.h +index 4f2eaca..1e01fe4 100644 +--- a/include/freetype/freetype.h ++++ b/include/freetype/freetype.h +@@ -1038,6 +1038,11 @@ FT_BEGIN_HEADER + * Especially for TrueType fonts see also the documentation for + * @FT_Size_Metrics. + */ ++ ++#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) ++#define generic GenericFromFreeTypeLibrary ++#endif ++ + typedef struct FT_FaceRec_ + { + FT_Long num_faces; +@@ -1910,6 +1915,9 @@ FT_BEGIN_HEADER + + } FT_GlyphSlotRec; + ++#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) ++#undef generic ++#endif + + /*************************************************************************/ + /*************************************************************************/ +diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c +index 3f8619d..edf03b6 100644 +--- a/src/base/ftobjs.c ++++ b/src/base/ftobjs.c +@@ -528,6 +528,9 @@ + return error; + } + ++#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) ++#define generic GenericFromFreeTypeLibrary ++#endif + + static void + ft_glyphslot_clear( FT_GlyphSlot slot ) +@@ -1195,6 +1198,9 @@ + FT_FREE( face ); + } + ++#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) ++#undef generic ++#endif + + static void + Destroy_Driver( FT_Driver driver ) diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/brotli-static.patch b/Meta/CMake/vcpkg/overlay-ports/freetype/brotli-static.patch new file mode 100644 index 00000000000..d872e8dfecd --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/brotli-static.patch @@ -0,0 +1,21 @@ +diff --git a/builds/cmake/FindBrotliDec.cmake b/builds/cmake/FindBrotliDec.cmake +index 46356b1fd..ed4cc2409 100644 +--- a/builds/cmake/FindBrotliDec.cmake ++++ b/builds/cmake/FindBrotliDec.cmake +@@ -35,10 +35,15 @@ find_path(BROTLIDEC_INCLUDE_DIRS + PATH_SUFFIXES brotli) + + find_library(BROTLIDEC_LIBRARIES +- NAMES brotlidec ++ NAMES brotlidec brotlidec-static NAMES_PER_DIR + HINTS ${PC_BROTLIDEC_LIBDIR} + ${PC_BROTLIDEC_LIBRARY_DIRS}) + ++ find_library(BROTLICOMMON_LIBRARIES ++ NAMES brotlicommon-static brotlicommon NAMES_PER_DIR ++ HINTS ${PC_BROTLIDEC_LIBDIR} ++ ${PC_BROTLIDEC_LIBRARY_DIRS}) ++ set(BROTLIDEC_LIBRARIES "${BROTLIDEC_LIBRARIES};${BROTLICOMMON_LIBRARIES}") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args( diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/bzip2.patch b/Meta/CMake/vcpkg/overlay-ports/freetype/bzip2.patch new file mode 100644 index 00000000000..c0f9101f758 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/bzip2.patch @@ -0,0 +1,13 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 469a141a2..eec19c7d0 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -517,7 +517,7 @@ if (BZIP2_FOUND) + if (PC_BZIP2_FOUND) + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "bzip2") + else () +- list(APPEND PKGCONFIG_LIBS_PRIVATE "-lbz2") ++ list(APPEND PKGCONFIG_REQUIRES_PRIVATE "bzip2") + endif () + endif () + if (PNG_FOUND) diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/fix-exports.patch b/Meta/CMake/vcpkg/overlay-ports/freetype/fix-exports.patch new file mode 100644 index 00000000000..7b12dc48772 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/fix-exports.patch @@ -0,0 +1,40 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index cb1b9a0f2..edca5d579 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -508,7 +508,6 @@ set(PKG_CONFIG_REQUIRED_PRIVATE "") + set(PKGCONFIG_LIBS_PRIVATE "") + + if (ZLIB_FOUND) +- target_link_libraries(freetype PRIVATE ${ZLIB_LIBRARIES}) ++ target_link_libraries(freetype PRIVATE ZLIB::ZLIB) +- target_include_directories(freetype PRIVATE ${ZLIB_INCLUDE_DIRS}) + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "zlib") + endif () +@@ -596,12 +596,25 @@ if (NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL) + install( + EXPORT freetype-targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/freetype +- FILE freetype-config.cmake + COMPONENT headers) + install( + FILES ${PROJECT_BINARY_DIR}/freetype-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/freetype + COMPONENT headers) ++ ++ if(ZLIB_FOUND AND BUILD_SHARED_LIBS) ++ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/freetype-config.cmake" ++[[include(CMakeFindDependencyMacro) ++find_dependency(ZLIB) ++include("${CMAKE_CURRENT_LIST_DIR}/freetype-targets.cmake") ++]]) ++ else() ++ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/freetype-config.cmake" ++[[include("${CMAKE_CURRENT_LIST_DIR}/freetype-targets.cmake") ++]]) ++ endif() ++ ++ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freetype-config.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/freetype) + endif () + + diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/portfile.cmake b/Meta/CMake/vcpkg/overlay-ports/freetype/portfile.cmake new file mode 100644 index 00000000000..0c11e5998e1 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/portfile.cmake @@ -0,0 +1,98 @@ +if("subpixel-rendering" IN_LIST FEATURES) + set(SUBPIXEL_RENDERING_PATCH "subpixel-rendering.patch") +endif() + +string(REPLACE "." "-" VERSION_HYPHEN "${VERSION}") + +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com/ + OUT_SOURCE_PATH SOURCE_PATH + REPO freetype/freetype + REF "VER-${VERSION_HYPHEN}" + SHA512 fccfaa15eb79a105981bf634df34ac9ddf1c53550ec0b334903a1b21f9f8bf5eb2b3f9476e554afa112a0fca58ec85ab212d674dfd853670efec876bacbe8a53 + HEAD_REF master + PATCHES + 0003-Fix-UWP.patch + brotli-static.patch + bzip2.patch + fix-exports.patch + ${SUBPIXEL_RENDERING_PATCH} +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + zlib FT_REQUIRE_ZLIB + bzip2 FT_REQUIRE_BZIP2 + error-strings FT_ENABLE_ERROR_STRINGS + png FT_REQUIRE_PNG + brotli FT_REQUIRE_BROTLI + INVERTED_FEATURES + zlib FT_DISABLE_ZLIB + bzip2 FT_DISABLE_BZIP2 + png FT_DISABLE_PNG + brotli FT_DISABLE_BROTLI +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DFT_DISABLE_HARFBUZZ=ON + ${FEATURE_OPTIONS} +) + +vcpkg_cmake_install() +vcpkg_copy_pdbs() +vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/freetype) + +# Rename for easy usage (VS integration; CMake and autotools will not care) +file(RENAME "${CURRENT_PACKAGES_DIR}/include/freetype2/freetype" "${CURRENT_PACKAGES_DIR}/include/freetype") +file(RENAME "${CURRENT_PACKAGES_DIR}/include/freetype2/ft2build.h" "${CURRENT_PACKAGES_DIR}/include/ft2build.h") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/include/freetype2") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") + +# Fix the include dir [freetype2 -> freetype] +file(READ "${CURRENT_PACKAGES_DIR}/share/freetype/freetype-targets.cmake" CONFIG_MODULE) +string(REPLACE "\${_IMPORT_PREFIX}/include/freetype2" "\${_IMPORT_PREFIX}/include" CONFIG_MODULE "${CONFIG_MODULE}") +string(REPLACE "\${_IMPORT_PREFIX}/lib/brotlicommon-static.lib" [[\$<\$>:${_IMPORT_PREFIX}/lib/brotlicommon-static.lib>;\$<\$:${_IMPORT_PREFIX}/debug/lib/brotlicommon-static.lib>]] CONFIG_MODULE "${CONFIG_MODULE}") +string(REPLACE "\${_IMPORT_PREFIX}/lib/brotlidec-static.lib" [[\$<\$>:${_IMPORT_PREFIX}/lib/brotlidec-static.lib>;\$<\$:${_IMPORT_PREFIX}/debug/lib/brotlidec-static.lib>]] CONFIG_MODULE "${CONFIG_MODULE}") +string(REPLACE "\${_IMPORT_PREFIX}/lib/brotlidec.lib" [[\$<\$>:${_IMPORT_PREFIX}/lib/brotlidec.lib>;\$<\$:${_IMPORT_PREFIX}/debug/lib/brotlidec.lib>]] CONFIG_MODULE "${CONFIG_MODULE}") +string(REPLACE "\${_IMPORT_PREFIX}/lib/brotlidec.lib" [[\$<\$>:${_IMPORT_PREFIX}/lib/brotlidec.lib>;\$<\$:${_IMPORT_PREFIX}/debug/lib/brotlidec.lib>]] CONFIG_MODULE "${CONFIG_MODULE}") +file(WRITE ${CURRENT_PACKAGES_DIR}/share/freetype/freetype-targets.cmake "${CONFIG_MODULE}") + +find_library(FREETYPE_DEBUG NAMES freetyped PATHS "${CURRENT_PACKAGES_DIR}/debug/lib/" NO_DEFAULT_PATH) +if(NOT VCPKG_BUILD_TYPE) + file(READ "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/freetype2.pc" _contents) + if(FREETYPE_DEBUG) + string(REPLACE "-lfreetype" "-lfreetyped" _contents "${_contents}") + endif() + string(REPLACE "-I\${includedir}/freetype2" "-I\${includedir}" _contents "${_contents}") + file(WRITE "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/freetype2.pc" "${_contents}") +endif() + +file(READ "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/freetype2.pc" _contents) +string(REPLACE "-I\${includedir}/freetype2" "-I\${includedir}" _contents "${_contents}") +file(WRITE "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/freetype2.pc" "${_contents}") + + +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") + +if(VCPKG_TARGET_IS_WINDOWS) + set(dll_linkage 1) + if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") + set(dll_linkage 0) + endif() + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/freetype/config/public-macros.h" "#elif defined( DLL_IMPORT )" "#elif ${dll_linkage}") +endif() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" + "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright( + FILE_LIST + "${SOURCE_PATH}/LICENSE.TXT" + "${SOURCE_PATH}/docs/FTL.TXT" + "${SOURCE_PATH}/docs/GPLv2.TXT" +) diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/subpixel-rendering.patch b/Meta/CMake/vcpkg/overlay-ports/freetype/subpixel-rendering.patch new file mode 100644 index 00000000000..980b782c02c --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/subpixel-rendering.patch @@ -0,0 +1,13 @@ +diff --git a/include/freetype/config/ftoption.h b/include/freetype/config/ftoption.h +index 1976b33af959..b3425e55feec 100644 +--- a/include/freetype/config/ftoption.h ++++ b/include/freetype/config/ftoption.h +@@ -123,7 +123,7 @@ FT_BEGIN_HEADER + * When this macro is not defined, FreeType offers alternative LCD + * rendering technology that produces excellent output. + */ +-/* #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING */ ++#define FT_CONFIG_OPTION_SUBPIXEL_RENDERING + + + /************************************************************************** diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/usage b/Meta/CMake/vcpkg/overlay-ports/freetype/usage new file mode 100644 index 00000000000..ca71e4443b9 --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/usage @@ -0,0 +1,4 @@ +freetype is compatible with built-in CMake targets: + + find_package(Freetype REQUIRED) + target_link_libraries(main PRIVATE Freetype::Freetype) # since CMake 3.10 diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg-cmake-wrapper.cmake b/Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg-cmake-wrapper.cmake new file mode 100644 index 00000000000..1be0faa225b --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg-cmake-wrapper.cmake @@ -0,0 +1,95 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0054 NEW) + +list(REMOVE_ITEM ARGS "NO_MODULE" "CONFIG" "MODULE") +_find_package(${ARGS} CONFIG) + +if(Freetype_FOUND) + include("${CMAKE_ROOT}/Modules/SelectLibraryConfigurations.cmake") + + get_target_property(_freetype_include_dirs freetype INTERFACE_INCLUDE_DIRECTORIES) + + if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + get_target_property(_freetype_location_debug freetype IMPORTED_IMPLIB_DEBUG) + get_target_property(_freetype_location_release freetype IMPORTED_IMPLIB_RELEASE) + endif() + if(NOT _freetype_location_debug AND NOT _freetype_location_release) + get_target_property(_freetype_location_debug freetype IMPORTED_LOCATION_DEBUG) + get_target_property(_freetype_location_release freetype IMPORTED_LOCATION_RELEASE) + endif() + + set(FREETYPE_FOUND TRUE) + + set(FREETYPE_INCLUDE_DIRS "${_freetype_include_dirs}") + set(FREETYPE_INCLUDE_DIR_ft2build "${_freetype_include_dirs}") + set(FREETYPE_INCLUDE_DIR_freetype2 "${_freetype_include_dirs}") + set(FREETYPE_LIBRARY_DEBUG "${_freetype_location_debug}" CACHE INTERNAL "vcpkg") + set(FREETYPE_LIBRARY_RELEASE "${_freetype_location_release}" CACHE INTERNAL "vcpkg") + select_library_configurations(FREETYPE) + set(FREETYPE_LIBRARIES ${FREETYPE_LIBRARY}) + set(FREETYPE_VERSION_STRING "${Freetype_VERSION}") + + unset(_freetype_include_dirs) + unset(_freetype_location_debug) + unset(_freetype_location_release) +endif() + +if("@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + if("@FT_REQUIRE_ZLIB@") + find_package(ZLIB) + endif() + if("@FT_REQUIRE_BZIP2@") + find_package(BZip2) + endif() + if("@FT_REQUIRE_PNG@") + find_package(PNG) + endif() + if("@FT_REQUIRE_BROTLI@") + find_library(BROTLIDEC_LIBRARY_RELEASE NAMES brotlidec brotlidec-static PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}" PATH_SUFFIXES lib NO_DEFAULT_PATH) + find_library(BROTLIDEC_LIBRARY_DEBUG NAMES brotlidec brotlidec-static brotlidecd brotlidec-staticd PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug" PATH_SUFFIXES lib NO_DEFAULT_PATH) + find_library(BROTLICOMMON_LIBRARY_RELEASE NAMES brotlicommon brotlicommon-static PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}" PATH_SUFFIXES lib NO_DEFAULT_PATH) + find_library(BROTLICOMMON_LIBRARY_DEBUG NAMES brotlicommon brotlicommon-static brotlicommond brotlicommon-staticd PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug" PATH_SUFFIXES lib NO_DEFAULT_PATH) + include(SelectLibraryConfigurations) + select_library_configurations(BROTLIDEC) + select_library_configurations(BROTLICOMMON) + endif("@FT_REQUIRE_BROTLI@") + + if(TARGET Freetype::Freetype) + if("@FT_REQUIRE_ZLIB@") + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES ZLIB::ZLIB) + endif() + if("@FT_REQUIRE_BZIP2@") + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES BZip2::BZip2) + endif() + if("@FT_REQUIRE_PNG@") + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES PNG::PNG) + endif() + if("@FT_REQUIRE_BROTLI@") + if(BROTLIDEC_LIBRARY_DEBUG) + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES "\$<\$:${BROTLIDEC_LIBRARY_DEBUG}>") + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES "\$<\$:${BROTLICOMMON_LIBRARY_DEBUG}>") + endif() + if(BROTLIDEC_LIBRARY_RELEASE) + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES "\$<\$>:${BROTLIDEC_LIBRARY_RELEASE}>") + set_property(TARGET Freetype::Freetype APPEND PROPERTY INTERFACE_LINK_LIBRARIES "\$<\$>:${BROTLICOMMON_LIBRARY_RELEASE}>") + endif() + endif() + endif() + + if(FREETYPE_LIBRARIES) + if("@FT_REQUIRE_ZLIB@") + list(APPEND FREETYPE_LIBRARIES ${ZLIB_LIBRARIES}) + endif() + if("@FT_REQUIRE_BZIP2@") + list(APPEND FREETYPE_LIBRARIES ${BZIP2_LIBRARIES}) + endif() + if("@FT_REQUIRE_PNG@") + list(APPEND FREETYPE_LIBRARIES ${PNG_LIBRARIES}) + endif() + if("@FT_REQUIRE_BROTLI@") + list(APPEND FREETYPE_LIBRARIES ${BROTLIDEC_LIBRARIES} ${BROTLICOMMON_LIBRARIES}) + endif() + endif() +endif() +cmake_policy(POP) \ No newline at end of file diff --git a/Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg.json b/Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg.json new file mode 100644 index 00000000000..7181e99080b --- /dev/null +++ b/Meta/CMake/vcpkg/overlay-ports/freetype/vcpkg.json @@ -0,0 +1,55 @@ +{ + "name": "freetype", + "version": "2.13.3", + "description": "A library to render fonts.", + "homepage": "https://www.freetype.org/", + "license": "FTL OR GPL-2.0-or-later", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "brotli", + "bzip2", + "png", + "zlib" + ], + "features": { + "brotli": { + "description": "Support decompression of WOFF2 streams", + "dependencies": [ + "brotli" + ] + }, + "bzip2": { + "description": "Support bzip2 compressed fonts.", + "dependencies": [ + "bzip2" + ] + }, + "error-strings": { + "description": "Enable support for meaningful error descriptions." + }, + "png": { + "description": "Support PNG compressed OpenType embedded bitmaps.", + "dependencies": [ + "libpng" + ] + }, + "subpixel-rendering": { + "description": "Enables subpixel rendering." + }, + "zlib": { + "description": "Use zlib instead of internal library for DEFLATE", + "dependencies": [ + "zlib" + ] + } + } +} From 2022c9e67943482d7ca5806942c02189ec87adaf Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 9 Mar 2025 13:14:39 +1300 Subject: [PATCH 015/141] Tests/LibWeb: Import the WPT URL test suite --- .../wpt-import/url/IdnaTestV2.window.txt | 2674 ++++ .../wpt-import/url/a-element-origin.txt | 400 + .../wpt-import/url/url-constructor.any.txt | 880 ++ .../wpt-import/url/url-origin.any.txt | 401 + .../url/url-setters-a-area.window.txt | Bin 0 -> 43745 bytes .../url/url-setters-stripping.any.txt | 265 + .../wpt-import/url/url-setters.any.txt | Bin 0 -> 21515 bytes .../wpt-import/url/url-statics-parse.any.txt | 13 + .../wpt-import/url/url-tojson.any.txt | 6 + .../wpt-import/url/urlencoded-parser.any.txt | 110 + .../url/urlsearchparams-delete.any.txt | 13 + .../url/urlsearchparams-foreach.any.txt | 11 + .../url/urlsearchparams-get.any.txt | 7 + .../url/urlsearchparams-getall.any.txt | 7 + .../url/urlsearchparams-has.any.txt | 9 + .../url/urlsearchparams-set.any.txt | 7 + .../url/urlsearchparams-sort.any.txt | 22 + .../url/urlsearchparams-stringifier.any.txt | 19 + .../wpt-import/common/subset-tests-by-key.js | 83 + .../wpt-import/url/IdnaTestV2.window.html | 8 + .../input/wpt-import/url/IdnaTestV2.window.js | 24 + .../wpt-import/url/a-element-origin.html | 12 + .../wpt-import/url/resources/IdnaTestV2.json | 12803 ++++++++++++++++ .../url/resources/a-element-origin.js | 39 + .../url/resources/setters_tests.json | 2424 +++ .../urltestdata-javascript-only.json | 18 + .../wpt-import/url/resources/urltestdata.json | 10122 ++++++++++++ .../wpt-import/url/url-constructor.any.html | 15 + .../wpt-import/url/url-constructor.any.js | 56 + .../input/wpt-import/url/url-origin.any.html | 15 + .../input/wpt-import/url/url-origin.any.js | 19 + .../url/url-setters-a-area.window.html | 8 + .../url/url-setters-a-area.window.js | 43 + .../url/url-setters-stripping.any.html | 15 + .../url/url-setters-stripping.any.js | 125 + .../input/wpt-import/url/url-setters.any.html | 15 + .../input/wpt-import/url/url-setters.any.js | 34 + .../wpt-import/url/url-statics-parse.any.html | 15 + .../wpt-import/url/url-statics-parse.any.js | 50 + .../input/wpt-import/url/url-tojson.any.html | 15 + .../input/wpt-import/url/url-tojson.any.js | 4 + .../wpt-import/url/urlencoded-parser.any.html | 15 + .../wpt-import/url/urlencoded-parser.any.js | 68 + .../url/urlsearchparams-delete.any.html | 15 + .../url/urlsearchparams-delete.any.js | 83 + .../url/urlsearchparams-foreach.any.html | 15 + .../url/urlsearchparams-foreach.any.js | 76 + .../url/urlsearchparams-get.any.html | 15 + .../wpt-import/url/urlsearchparams-get.any.js | 21 + .../url/urlsearchparams-getall.any.html | 15 + .../url/urlsearchparams-getall.any.js | 25 + .../url/urlsearchparams-has.any.html | 15 + .../wpt-import/url/urlsearchparams-has.any.js | 45 + .../url/urlsearchparams-set.any.html | 15 + .../wpt-import/url/urlsearchparams-set.any.js | 22 + .../url/urlsearchparams-sort.any.html | 15 + .../url/urlsearchparams-sort.any.js | 62 + .../url/urlsearchparams-stringifier.any.html | 15 + .../url/urlsearchparams-stringifier.any.js | 145 + 59 files changed, 31488 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/IdnaTestV2.window.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-setters-a-area.window.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-setters-stripping.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-setters.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-statics-parse.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/url-tojson.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlencoded-parser.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-delete.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-foreach.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-get.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-getall.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-has.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-set.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-sort.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/url/urlsearchparams-stringifier.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/common/subset-tests-by-key.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/a-element-origin.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/resources/IdnaTestV2.json create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/resources/a-element-origin.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/resources/setters_tests.json create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata-javascript-only.json create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.js diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/IdnaTestV2.window.txt b/Tests/LibWeb/Text/expected/wpt-import/url/IdnaTestV2.window.txt new file mode 100644 index 00000000000..9c201ce9bc9 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/url/IdnaTestV2.window.txt @@ -0,0 +1,2674 @@ +Harness status: OK + +Found 2669 tests + +2669 Pass +Pass Loading data… +Pass ToASCII("fass.de") +Pass ToASCII("faß.de") +Pass ToASCII("Faß.de") +Pass ToASCII("xn--fa-hia.de") +Pass ToASCII("à.א̈") +Pass ToASCII("à.א̈") +Pass ToASCII("À.א̈") +Pass ToASCII("À.א̈") +Pass ToASCII("xn--0ca.xn--ssa73l") +Pass ToASCII("à̈.א") +Pass ToASCII("à̈.א") +Pass ToASCII("À̈.א") +Pass ToASCII("À̈.א") +Pass ToASCII("xn--0ca81i.xn--4db") +Pass ToASCII("a‌b") C1 +Pass ToASCII("A‌B") C1 +Pass ToASCII("A‌b") C1 +Pass ToASCII("ab") +Pass ToASCII("xn--ab-j1t") C1 +Pass ToASCII("a्‌b") +Pass ToASCII("A्‌B") +Pass ToASCII("A्‌b") +Pass ToASCII("xn--ab-fsf") +Pass ToASCII("a्b") +Pass ToASCII("A्B") +Pass ToASCII("A्b") +Pass ToASCII("xn--ab-fsf604u") +Pass ToASCII("a‍b") C2 +Pass ToASCII("A‍B") C2 +Pass ToASCII("A‍b") C2 +Pass ToASCII("xn--ab-m1t") C2 +Pass ToASCII("a्‍b") +Pass ToASCII("A्‍B") +Pass ToASCII("A्‍b") +Pass ToASCII("xn--ab-fsf014u") +Pass ToASCII("¡") +Pass ToASCII("xn--7a") +Pass ToASCII("᧚") +Pass ToASCII("xn--pkf") +Pass ToASCII("。") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII(".") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("ꭠ") +Pass ToASCII("xn--3y9a") +Pass ToASCII("1234567890ä1234567890123456789012345678901234567890123456") A4_2 (ignored) +Pass ToASCII("1234567890ä1234567890123456789012345678901234567890123456") A4_2 (ignored) +Pass ToASCII("1234567890Ä1234567890123456789012345678901234567890123456") A4_2 (ignored) +Pass ToASCII("1234567890Ä1234567890123456789012345678901234567890123456") A4_2 (ignored) +Pass ToASCII("xn--12345678901234567890123456789012345678901234567890123456-fxe") A4_2 (ignored) +Pass ToASCII("www.eXample.cOm") +Pass ToASCII("Bücher.de") +Pass ToASCII("Bücher.de") +Pass ToASCII("bücher.de") +Pass ToASCII("bücher.de") +Pass ToASCII("BÜCHER.DE") +Pass ToASCII("BÜCHER.DE") +Pass ToASCII("xn--bcher-kva.de") +Pass ToASCII("ÖBB") +Pass ToASCII("ÖBB") +Pass ToASCII("öbb") +Pass ToASCII("öbb") +Pass ToASCII("Öbb") +Pass ToASCII("Öbb") +Pass ToASCII("xn--bb-eka") +Pass ToASCII("FAẞ.de") +Pass ToASCII("FAẞ.DE") +Pass ToASCII("βόλος.com") +Pass ToASCII("βόλος.com") +Pass ToASCII("ΒΌΛΟΣ.COM") +Pass ToASCII("ΒΌΛΟΣ.COM") +Pass ToASCII("βόλοσ.com") +Pass ToASCII("βόλοσ.com") +Pass ToASCII("Βόλοσ.com") +Pass ToASCII("Βόλοσ.com") +Pass ToASCII("xn--nxasmq6b.com") +Pass ToASCII("Βόλος.com") +Pass ToASCII("Βόλος.com") +Pass ToASCII("xn--nxasmm1c.com") +Pass ToASCII("xn--nxasmm1c") +Pass ToASCII("βόλος") +Pass ToASCII("βόλος") +Pass ToASCII("ΒΌΛΟΣ") +Pass ToASCII("ΒΌΛΟΣ") +Pass ToASCII("βόλοσ") +Pass ToASCII("βόλοσ") +Pass ToASCII("Βόλοσ") +Pass ToASCII("Βόλοσ") +Pass ToASCII("xn--nxasmq6b") +Pass ToASCII("Βόλος") +Pass ToASCII("Βόλος") +Pass ToASCII("www.ශ්‍රී.com") +Pass ToASCII("WWW.ශ්‍රී.COM") +Pass ToASCII("Www.ශ්‍රී.com") +Pass ToASCII("www.xn--10cl1a0b.com") +Pass ToASCII("www.ශ්රී.com") +Pass ToASCII("WWW.ශ්රී.COM") +Pass ToASCII("Www.ශ්රී.com") +Pass ToASCII("www.xn--10cl1a0b660p.com") +Pass ToASCII("نامه‌ای") +Pass ToASCII("xn--mgba3gch31f") +Pass ToASCII("نامهای") +Pass ToASCII("xn--mgba3gch31f060k") +Pass ToASCII("xn--mgba3gch31f060k.com") +Pass ToASCII("نامه‌ای.com") +Pass ToASCII("نامه‌ای.COM") +Pass ToASCII("xn--mgba3gch31f.com") +Pass ToASCII("نامهای.com") +Pass ToASCII("نامهای.COM") +Pass ToASCII("a.b.c。d。") A4_2 (ignored) +Pass ToASCII("a.b.c。d。") A4_2 (ignored) +Pass ToASCII("A.B.C。D。") A4_2 (ignored) +Pass ToASCII("A.b.c。D。") A4_2 (ignored) +Pass ToASCII("a.b.c.d.") A4_2 (ignored) +Pass ToASCII("A.B.C。D。") A4_2 (ignored) +Pass ToASCII("A.b.c。D。") A4_2 (ignored) +Pass ToASCII("Ü.xn--tda") +Pass ToASCII("Ü.xn--tda") +Pass ToASCII("ü.xn--tda") +Pass ToASCII("ü.xn--tda") +Pass ToASCII("Ü.XN--TDA") +Pass ToASCII("Ü.XN--TDA") +Pass ToASCII("Ü.xn--Tda") +Pass ToASCII("Ü.xn--Tda") +Pass ToASCII("xn--tda.xn--tda") +Pass ToASCII("ü.ü") +Pass ToASCII("ü.ü") +Pass ToASCII("Ü.Ü") +Pass ToASCII("Ü.Ü") +Pass ToASCII("Ü.ü") +Pass ToASCII("Ü.ü") +Pass ToASCII("xn--u-ccb") V1 +Pass ToASCII("a⒈com") V7 +Pass ToASCII("a1.com") +Pass ToASCII("A⒈COM") V7 +Pass ToASCII("A⒈Com") V7 +Pass ToASCII("xn--acom-0w1b") V7 +Pass ToASCII("xn--a-ecp.ru") V7 +Pass ToASCII("xn--0.pt") P4 +Pass ToASCII("xn--a.pt") V7 +Pass ToASCII("xn--a-Ä.pt") P4 +Pass ToASCII("xn--a-Ä.pt") P4 +Pass ToASCII("xn--a-ä.pt") P4 +Pass ToASCII("xn--a-ä.pt") P4 +Pass ToASCII("XN--A-Ä.PT") P4 +Pass ToASCII("XN--A-Ä.PT") P4 +Pass ToASCII("Xn--A-Ä.pt") P4 +Pass ToASCII("Xn--A-Ä.pt") P4 +Pass ToASCII("xn--xn--a--gua.pt") V4; V2 (ignored) +Pass ToASCII("日本語。JP") +Pass ToASCII("日本語。JP") +Pass ToASCII("日本語。jp") +Pass ToASCII("日本語。Jp") +Pass ToASCII("xn--wgv71a119e.jp") +Pass ToASCII("日本語.jp") +Pass ToASCII("日本語.JP") +Pass ToASCII("日本語.Jp") +Pass ToASCII("日本語。jp") +Pass ToASCII("日本語。Jp") +Pass ToASCII("☕") +Pass ToASCII("xn--53h") +Pass ToASCII("1.aß‌‍b‌‍cßßßßdςσßßßßßßßßeßßßßßßßßßßxßßßßßßßßßßyßßßßßßßß̂ßz") C1; C2; A4_2 (ignored) +Pass ToASCII("1.ASS‌‍B‌‍CSSSSSSSSDΣΣSSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSŜSSZ") C1; C2; A4_2 (ignored) +Pass ToASCII("1.ASS‌‍B‌‍CSSSSSSSSDΣΣSSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSŜSSZ") C1; C2; A4_2 (ignored) +Pass ToASCII("1.ass‌‍b‌‍cssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") C1; C2; A4_2 (ignored) +Pass ToASCII("1.ass‌‍b‌‍cssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") C1; C2; A4_2 (ignored) +Pass ToASCII("1.Ass‌‍b‌‍cssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") C1; C2; A4_2 (ignored) +Pass ToASCII("1.Ass‌‍b‌‍cssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") C1; C2; A4_2 (ignored) +Pass ToASCII("1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa") A4_2 (ignored) +Pass ToASCII("1.assbcssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") A4_2 (ignored) +Pass ToASCII("1.assbcssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") A4_2 (ignored) +Pass ToASCII("1.ASSBCSSSSSSSSDΣΣSSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSŜSSZ") A4_2 (ignored) +Pass ToASCII("1.ASSBCSSSSSSSSDΣΣSSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSŜSSZ") A4_2 (ignored) +Pass ToASCII("1.Assbcssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") A4_2 (ignored) +Pass ToASCII("1.Assbcssssssssdσσssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssŝssz") A4_2 (ignored) +Pass ToASCII("1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa69989dba9gc") C1; C2; A4_2 (ignored) +Pass ToASCII("1.Aß‌‍b‌‍cßßßßdςσßßßßßßßßeßßßßßßßßßßxßßßßßßßßßßyßßßßßßßß̂ßz") C1; C2; A4_2 (ignored) +Pass ToASCII("1.xn--abcdexyz-qyacaaabaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaa010ze2isb1140zba8cc") C1; C2; A4_2 (ignored) +Pass ToASCII("‌x‍n‌-‍-bß") C1; C2 +Pass ToASCII("‌X‍N‌-‍-BSS") C1; C2 +Pass ToASCII("‌x‍n‌-‍-bss") C1; C2 +Pass ToASCII("‌X‍n‌-‍-Bss") C1; C2 +Pass ToASCII("xn--bss") +Pass ToASCII("夙") +Pass ToASCII("xn--xn--bss-7z6ccid") C1; C2 +Pass ToASCII("‌X‍n‌-‍-Bß") C1; C2 +Pass ToASCII("xn--xn--b-pqa5796ccahd") C1; C2 +Pass ToASCII("ˣ͏ℕ​﹣­-᠌ℬ︀ſ⁤U+dd30U+ddefffl") +Pass ToASCII("x͏N​-­-᠌B︀s⁤sU+ddefffl") +Pass ToASCII("x͏n​-­-᠌b︀s⁤sU+ddefffl") +Pass ToASCII("X͏N​-­-᠌B︀S⁤SU+ddefFFL") +Pass ToASCII("X͏n​-­-᠌B︀s⁤sU+ddefffl") +Pass ToASCII("xn--bssffl") +Pass ToASCII("夡夞夜夙") +Pass ToASCII("ˣ͏ℕ​﹣­-᠌ℬ︀S⁤U+dd30U+ddefFFL") +Pass ToASCII("x͏N​-­-᠌B︀S⁤sU+ddefFFL") +Pass ToASCII("ˣ͏ℕ​﹣­-᠌ℬ︀s⁤U+dd30U+ddefffl") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("ä1234567890123456789012345678901234567890123456789012345") +Pass ToASCII("ä1234567890123456789012345678901234567890123456789012345") +Pass ToASCII("Ä1234567890123456789012345678901234567890123456789012345") +Pass ToASCII("Ä1234567890123456789012345678901234567890123456789012345") +Pass ToASCII("xn--1234567890123456789012345678901234567890123456789012345-9te") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901C") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901C") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("a.b..-q--a-.e") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("a.b..-q--ä-.e") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("a.b..-q--ä-.e") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("A.B..-Q--Ä-.E") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("A.B..-Q--Ä-.E") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("A.b..-Q--Ä-.E") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("A.b..-Q--Ä-.E") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("a.b..xn---q----jra.e") V2 (ignored); V3 (ignored); A4_2 (ignored) +Pass ToASCII("a..c") A4_2 (ignored) +Pass ToASCII("a.-b.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("a.b-.c") V3 (ignored) +Pass ToASCII("a.-.c") V3 (ignored) +Pass ToASCII("a.bc--de.f") V2 (ignored) +Pass ToASCII("xn--xn---epa") V4; V2 (ignored) +Pass ToASCII("ä.­.c") A4_2 (ignored) +Pass ToASCII("ä.­.c") A4_2 (ignored) +Pass ToASCII("Ä.­.C") A4_2 (ignored) +Pass ToASCII("Ä.­.C") A4_2 (ignored) +Pass ToASCII("xn--4ca..c") A4_2 (ignored) +Pass ToASCII("ä.-b.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("ä.-b.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("Ä.-B.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("Ä.-B.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--4ca.-b.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("ä.b-.c") V3 (ignored) +Pass ToASCII("ä.b-.c") V3 (ignored) +Pass ToASCII("Ä.B-.C") V3 (ignored) +Pass ToASCII("Ä.B-.C") V3 (ignored) +Pass ToASCII("Ä.b-.C") V3 (ignored) +Pass ToASCII("Ä.b-.C") V3 (ignored) +Pass ToASCII("xn--4ca.b-.c") V3 (ignored) +Pass ToASCII("ä.-.c") V3 (ignored) +Pass ToASCII("ä.-.c") V3 (ignored) +Pass ToASCII("Ä.-.C") V3 (ignored) +Pass ToASCII("Ä.-.C") V3 (ignored) +Pass ToASCII("xn--4ca.-.c") V3 (ignored) +Pass ToASCII("ä.bc--de.f") V2 (ignored) +Pass ToASCII("ä.bc--de.f") V2 (ignored) +Pass ToASCII("Ä.BC--DE.F") V2 (ignored) +Pass ToASCII("Ä.BC--DE.F") V2 (ignored) +Pass ToASCII("Ä.bc--De.f") V2 (ignored) +Pass ToASCII("Ä.bc--De.f") V2 (ignored) +Pass ToASCII("xn--4ca.bc--de.f") V2 (ignored) +Pass ToASCII("a.b.̈c.d") V6 +Pass ToASCII("A.B.̈C.D") V6 +Pass ToASCII("A.b.̈c.d") V6 +Pass ToASCII("a.b.xn--c-bcb.d") V6 +Pass ToASCII("A0") +Pass ToASCII("0A") +Pass ToASCII("אׇ") +Pass ToASCII("xn--vdbr") +Pass ToASCII("א9ׇ") +Pass ToASCII("xn--9-ihcz") +Pass ToASCII("את") +Pass ToASCII("xn--4db6c") +Pass ToASCII("א׳ת") +Pass ToASCII("xn--4db6c0a") +Pass ToASCII("א7ת") +Pass ToASCII("xn--7-zhc3f") +Pass ToASCII("א٧ת") +Pass ToASCII("xn--4db6c6t") +Pass ToASCII("ஹ்‍") +Pass ToASCII("xn--dmc4b") +Pass ToASCII("ஹ்") +Pass ToASCII("xn--dmc4b194h") +Pass ToASCII("ஹ‍") C2 +Pass ToASCII("xn--dmc") +Pass ToASCII("ஹ") +Pass ToASCII("xn--dmc225h") C2 +Pass ToASCII("‍") C2 +Pass ToASCII("xn--1ug") C2 +Pass ToASCII("ஹ்‌") +Pass ToASCII("xn--dmc4by94h") +Pass ToASCII("ஹ‌") C1 +Pass ToASCII("xn--dmc025h") C1 +Pass ToASCII("‌") C1 +Pass ToASCII("xn--0ug") C1 +Pass ToASCII("لٰ‌ۭۯ") +Pass ToASCII("xn--ghb2gxqia") +Pass ToASCII("لٰۭۯ") +Pass ToASCII("xn--ghb2gxqia7523a") +Pass ToASCII("لٰ‌ۯ") +Pass ToASCII("xn--ghb2g3q") +Pass ToASCII("لٰۯ") +Pass ToASCII("xn--ghb2g3qq34f") +Pass ToASCII("ل‌ۭۯ") +Pass ToASCII("xn--ghb25aga") +Pass ToASCII("لۭۯ") +Pass ToASCII("xn--ghb25aga828w") +Pass ToASCII("ل‌ۯ") +Pass ToASCII("xn--ghb65a") +Pass ToASCII("لۯ") +Pass ToASCII("xn--ghb65a953d") +Pass ToASCII("xn--ghb2gxq") +Pass ToASCII("لٰۭ") +Pass ToASCII("ۯ‌ۯ") C1 +Pass ToASCII("xn--cmba") +Pass ToASCII("ۯۯ") +Pass ToASCII("xn--cmba004q") C1 +Pass ToASCII("xn--ghb") +Pass ToASCII("ل") +Pass ToASCII("a。。b") A4_2 (ignored) +Pass ToASCII("A。。B") A4_2 (ignored) +Pass ToASCII("a..b") A4_2 (ignored) +Pass ToASCII("..xn--skb") A4_2 (ignored) +Pass ToASCII("$") U1 (ignored) +Pass ToASCII("⑷.four") U1 (ignored) +Pass ToASCII("(4).four") U1 (ignored) +Pass ToASCII("⑷.FOUR") U1 (ignored) +Pass ToASCII("⑷.Four") U1 (ignored) +Pass ToASCII("aaU+d900z") V7; A3 +Pass ToASCII("AAU+d900Z") V7; A3 +Pass ToASCII("xn--") P4; A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("xn---") P4 +Pass ToASCII("xn--ASCII-") P4 +Pass ToASCII("ascii") +Pass ToASCII("xn--unicode-.org") P4 +Pass ToASCII("unicode.org") +Pass ToASCII("陋U+dc68U+dc74U+dd1fU+dd5fU+ddbf") +Pass ToASCII("陋㛼当U+dfab竮䗗") +Pass ToASCII("xn--snl253bgitxhzwu2arn60c") +Pass ToASCII("電U+df6a弳䎫窮䵗") +Pass ToASCII("xn--kbo60w31ob3z6t3av9z5b") +Pass ToASCII("xn--A-1ga") +Pass ToASCII("aö") +Pass ToASCII("aö") +Pass ToASCII("AÖ") +Pass ToASCII("AÖ") +Pass ToASCII("Aö") +Pass ToASCII("Aö") +Pass ToASCII("≠") +Pass ToASCII("≠") +Pass ToASCII("≠") +Pass ToASCII("xn--1ch") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c") A4_1 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.") A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("123456789012345678901234567890123456789012345678901234567890123.1234567890Ä1234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b") A4_1 (ignored); A4_2 (ignored) +Pass ToASCII("⒕∝ٟU+dd26.-U+dd2f") V7; V3 (ignored) +Pass ToASCII("14.∝ٟU+dd26.-U+dd2f") V7; V3 (ignored) +Pass ToASCII("14.xn--7hb713l3v90n.-") V7; V3 (ignored) +Pass ToASCII("xn--7hb713lfwbi1311b.-") V7; V3 (ignored) +Pass ToASCII("ꡣ.ߏ") +Pass ToASCII("xn--8c9a.xn--qsb") +Pass ToASCII("‍≠ᢙ≯.솣-ᡴႠ") C2 +Pass ToASCII("‍≠ᢙ≯.솣-ᡴႠ") C2 +Pass ToASCII("‍≠ᢙ≯.솣-ᡴⴀ") C2 +Pass ToASCII("‍≠ᢙ≯.솣-ᡴⴀ") C2 +Pass ToASCII("xn--jbf911clb.xn----p9j493ivi4l") +Pass ToASCII("≠ᢙ≯.솣-ᡴⴀ") +Pass ToASCII("≠ᢙ≯.솣-ᡴⴀ") +Pass ToASCII("≠ᢙ≯.솣-ᡴႠ") +Pass ToASCII("≠ᢙ≯.솣-ᡴႠ") +Pass ToASCII("xn--jbf929a90b0b.xn----p9j493ivi4l") C2 +Pass ToASCII("xn--jbf911clb.xn----6zg521d196p") V7 +Pass ToASCII("xn--jbf929a90b0b.xn----6zg521d196p") C2; V7 +Pass ToASCII("U+df9c.U+dfc7ྡྷݽ؀") V7 +Pass ToASCII("U+df9c.U+dfc7ྡྷݽ؀") V7 +Pass ToASCII("U+df9c.U+dfc7ྡྷݽ؀") V7 +Pass ToASCII("xn--gw68a.xn--ifb57ev2psc6027m") V7 +Pass ToASCII("U+dcd4̃.U+dcc2") V6 +Pass ToASCII("xn--nsa95820a.xn--wz1d") V6 +Pass ToASCII("‌U+dfad.ႲU+ddc0") C1; V7 +Pass ToASCII("‌U+dfad.ⴒU+ddc0") C1; V7 +Pass ToASCII("xn--bn95b.xn--9kj2034e") V7 +Pass ToASCII("xn--0ug15083f.xn--9kj2034e") C1; V7 +Pass ToASCII("xn--bn95b.xn--qnd6272k") V7 +Pass ToASCII("xn--0ug15083f.xn--qnd6272k") C1; V7 +Pass ToASCII("繱U+ddbf‍.8︒") V7 +Pass ToASCII("xn--gl0as212a.i.") A4_2 (ignored) +Pass ToASCII("繱U+ddbf.i.") A4_2 (ignored) +Pass ToASCII("繱U+ddbf.I.") A4_2 (ignored) +Pass ToASCII("xn--1ug6928ac48e.i.") A4_2 (ignored) +Pass ToASCII("繱U+ddbf‍.i.") A4_2 (ignored) +Pass ToASCII("繱U+ddbf‍.I.") A4_2 (ignored) +Pass ToASCII("xn--gl0as212a.xn--8-o89h") V7 +Pass ToASCII("xn--1ug6928ac48e.xn--8-o89h") V7 +Pass ToASCII("U+ddbe.U+dc08") V6; A4_2 (ignored) +Pass ToASCII("U+ddbe.U+dc08") V6; A4_2 (ignored) +Pass ToASCII(".xn--ph4h") V6; A4_2 (ignored) +Pass ToASCII("ß۫。‍") C2 +Pass ToASCII("SS۫。‍") C2 +Pass ToASCII("ss۫。‍") C2 +Pass ToASCII("Ss۫。‍") C2 +Pass ToASCII("xn--ss-59d.") A4_2 (ignored) +Pass ToASCII("ss۫.") A4_2 (ignored) +Pass ToASCII("SS۫.") A4_2 (ignored) +Pass ToASCII("Ss۫.") A4_2 (ignored) +Pass ToASCII("xn--ss-59d.xn--1ug") C2 +Pass ToASCII("xn--zca012a.xn--1ug") C2 +Pass ToASCII("U+dc35‌⒈.U+df87") C1; V7 +Pass ToASCII("U+dc35‌1..U+df87") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--1-bs31m..xn--tv36e") V7; A4_2 (ignored) +Pass ToASCII("xn--1-rgn37671n..xn--tv36e") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--tshz2001k.xn--tv36e") V7 +Pass ToASCII("xn--0ug88o47900b.xn--tv36e") C1; V7 +Pass ToASCII("U+de23ٟꪲß。U+dce7") V7 +Pass ToASCII("U+de23ٟꪲSS。U+dce7") V7 +Pass ToASCII("U+de23ٟꪲss。U+dce7") V7 +Pass ToASCII("U+de23ٟꪲSs。U+dce7") V7 +Pass ToASCII("xn--ss-3xd2839nncy1m.xn--bb79d") V7 +Pass ToASCII("xn--zca92z0t7n5w96j.xn--bb79d") V7 +Pass ToASCII("ݴ‌U+dd3f。U+de10䉜‍U+dd3c") C1; C2; V7 +Pass ToASCII("ݴ‌U+dd1d。U+de10䉜‍U+dd3c") C1; C2; V7 +Pass ToASCII("xn--4pb2977v.xn--z0nt555ukbnv") V7 +Pass ToASCII("xn--4pb607jjt73a.xn--1ug236ke314donv1a") C1; C2; V7 +Pass ToASCII("ㅤ्Ⴀ័.᠋") V6; A4_2 (ignored) +Pass ToASCII("ᅠ्Ⴀ័.᠋") V6; A4_2 (ignored) +Pass ToASCII("ᅠ्ⴀ័.᠋") V6; A4_2 (ignored) +Pass ToASCII("xn--n3b445e53p.") V6; A4_2 (ignored) +Pass ToASCII("ㅤ्ⴀ័.᠋") V6; A4_2 (ignored) +Pass ToASCII("xn--n3b742bkqf4ty.") V7; A4_2 (ignored) +Pass ToASCII("xn--n3b468aoqa89r.") V7; A4_2 (ignored) +Pass ToASCII("xn--n3b445e53po6d.") V7; A4_2 (ignored) +Pass ToASCII("xn--n3b468azngju2a.") V7; A4_2 (ignored) +Pass ToASCII("❣‍.্U+dc3dؒꤩ") C2; V6 +Pass ToASCII("❣‍.্U+dc3dؒꤩ") C2; V6 +Pass ToASCII("xn--pei.xn--0fb32q3w7q2g4d") V6 +Pass ToASCII("xn--1ugy10a.xn--0fb32q3w7q2g4d") C2; V6 +Pass ToASCII("͉。U+dc6b") V6 +Pass ToASCII("xn--nua.xn--bc6k") V6 +Pass ToASCII("U+dc3fU+dd66.ᅠ") V6; A4_2 (ignored) +Pass ToASCII("U+dc3fU+dd66.ᅠ") V6; A4_2 (ignored) +Pass ToASCII("xn--ok3d.") V6; A4_2 (ignored) +Pass ToASCII("xn--ok3d.xn--psd") V6; V7 +Pass ToASCII("蔏。U+dc3a") V6 +Pass ToASCII("蔏。U+dc3a") V6 +Pass ToASCII("xn--uy1a.xn--jk3d") V6 +Pass ToASCII("xn--8g1d12120a.xn--5l6h") V7 +Pass ToASCII("U+dee7꧀2。㧉U+dd84") V6; V7 +Pass ToASCII("U+dee7꧀2。㧉U+dd84") V6; V7 +Pass ToASCII("xn--2-5z4eu89y.xn--97l02706d") V6; V7 +Pass ToASCII("⤸ςU+dc40。ᅠ") V7; A4_2 (ignored) +Pass ToASCII("⤸ςU+dc40。ᅠ") V7; A4_2 (ignored) +Pass ToASCII("⤸ΣU+dc40。ᅠ") V7; A4_2 (ignored) +Pass ToASCII("⤸σU+dc40。ᅠ") V7; A4_2 (ignored) +Pass ToASCII("xn--4xa192qmp03d.") V7; A4_2 (ignored) +Pass ToASCII("xn--3xa392qmp03d.") V7; A4_2 (ignored) +Pass ToASCII("⤸ΣU+dc40。ᅠ") V7; A4_2 (ignored) +Pass ToASCII("⤸σU+dc40。ᅠ") V7; A4_2 (ignored) +Pass ToASCII("xn--4xa192qmp03d.xn--psd") V7 +Pass ToASCII("xn--3xa392qmp03d.xn--psd") V7 +Pass ToASCII("xn--4xa192qmp03d.xn--cl7c") V7 +Pass ToASCII("xn--3xa392qmp03d.xn--cl7c") V7 +Pass ToASCII("‍U+dc56U+dc50.ֽU+dfb0ꡝU+dee1") C2; V6; V7 +Pass ToASCII("‍U+dc56U+dc50.ֽU+dfb0ꡝU+dee1") C2; V6; V7 +Pass ToASCII("xn--b726ey18m.xn--ldb8734fg0qcyzzg") V6; V7 +Pass ToASCII("xn--1ug66101lt8me.xn--ldb8734fg0qcyzzg") C2; V6; V7 +Pass ToASCII("。U+de35ςU+dc07。U+df88") V7; A4_2 (ignored) +Pass ToASCII("。U+de35ΣU+dc07。U+df88") V7; A4_2 (ignored) +Pass ToASCII("。U+de35σU+dc07。U+df88") V7; A4_2 (ignored) +Pass ToASCII(".xn--4xa68573c7n64d.xn--f29c") V7; A4_2 (ignored) +Pass ToASCII(".xn--3xa88573c7n64d.xn--f29c") V7; A4_2 (ignored) +Pass ToASCII("⒉U+de93≠。Ⴟ⬣Ⴈ") V7 +Pass ToASCII("⒉U+de93≠。Ⴟ⬣Ⴈ") V7 +Pass ToASCII("2.U+de93≠。Ⴟ⬣Ⴈ") V7 +Pass ToASCII("2.U+de93≠。Ⴟ⬣Ⴈ") V7 +Pass ToASCII("2.U+de93≠。ⴟ⬣ⴈ") V7 +Pass ToASCII("2.U+de93≠。ⴟ⬣ⴈ") V7 +Pass ToASCII("2.xn--1chz4101l.xn--45iz7d6b") V7 +Pass ToASCII("⒉U+de93≠。ⴟ⬣ⴈ") V7 +Pass ToASCII("⒉U+de93≠。ⴟ⬣ⴈ") V7 +Pass ToASCII("xn--1ch07f91401d.xn--45iz7d6b") V7 +Pass ToASCII("2.xn--1chz4101l.xn--gnd9b297j") V7 +Pass ToASCII("xn--1ch07f91401d.xn--gnd9b297j") V7 +Pass ToASCII("U+dd37.U+df90U+dc81U+de60ؤ") +Pass ToASCII("U+dd37.U+df90U+dc81U+de60ؤ") +Pass ToASCII("U+dd15.U+df90U+dc81U+de60ؤ") +Pass ToASCII("U+dd15.U+df90U+dc81U+de60ؤ") +Pass ToASCII("xn--ve6h.xn--jgb1694kz0b2176a") +Pass ToASCII("-U+de56ꡧ.U+de82U+dd83U+dd09") V7; V3 (ignored); U1 (ignored) +Pass ToASCII("-U+de56ꡧ.U+de82U+dd838,") V7; V3 (ignored); U1 (ignored) +Pass ToASCII("xn----hg4ei0361g.xn--8,-k362evu488a") V7; V3 (ignored); U1 (ignored) +Pass ToASCII("xn----hg4ei0361g.xn--207ht163h7m94c") V7; V3 (ignored) +Pass ToASCII("‌。͔") C1; V6 +Pass ToASCII("‌。͔") C1; V6 +Pass ToASCII(".xn--yua") V6; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--yua") C1; V6 +Pass ToASCII("U+dd25U+dd6e.ᡄႮ") +Pass ToASCII("U+dd25U+dd6e.ᡄႮ") +Pass ToASCII("U+dd25U+dd6e.ᡄⴎ") +Pass ToASCII("U+dd03U+dd6e.ᡄႮ") +Pass ToASCII("U+dd03U+dd6e.ᡄⴎ") +Pass ToASCII("xn--de6h.xn--37e857h") +Pass ToASCII("U+dd25.ᡄⴎ") +Pass ToASCII("U+dd03.ᡄႮ") +Pass ToASCII("U+dd03.ᡄⴎ") +Pass ToASCII("U+dd25U+dd6e.ᡄⴎ") +Pass ToASCII("U+dd03U+dd6e.ᡄႮ") +Pass ToASCII("U+dd03U+dd6e.ᡄⴎ") +Pass ToASCII("xn--de6h.xn--mnd799a") V7 +Pass ToASCII("U+dd25.ᡄႮ") +Pass ToASCII("ྤU+dd2f.U+dfedႻ") V6; V7 +Pass ToASCII("ྤU+dd2f.1Ⴛ") V6; V7 +Pass ToASCII("ྤU+dd2f.1ⴛ") V6; V7 +Pass ToASCII("xn--0fd40533g.xn--1-tws") V6; V7 +Pass ToASCII("ྤU+dd2f.U+dfedⴛ") V6; V7 +Pass ToASCII("xn--0fd40533g.xn--1-q1g") V6; V7 +Pass ToASCII("ςU+df0c8.U+df64") V7 +Pass ToASCII("ςU+df0c8.U+df64") V7 +Pass ToASCII("ΣU+df0c8.U+df64") V7 +Pass ToASCII("σU+df0c8.U+df64") V7 +Pass ToASCII("xn--8-zmb14974n.xn--su6h") V7 +Pass ToASCII("xn--8-xmb44974n.xn--su6h") V7 +Pass ToASCII("ΣU+df0c8.U+df64") V7 +Pass ToASCII("σU+df0c8.U+df64") V7 +Pass ToASCII("‌긃.榶-") C1; V3 (ignored) +Pass ToASCII("‌긃.榶-") C1; V3 (ignored) +Pass ToASCII("xn--ej0b.xn----d87b") V3 (ignored) +Pass ToASCII("xn--0ug3307c.xn----d87b") C1; V3 (ignored) +Pass ToASCII("뉓泓U+dd7d.্‍") V6 +Pass ToASCII("뉓泓U+dd7d.্‍") V6 +Pass ToASCII("xn--lwwp69lqs7m.xn--b7b") V6 +Pass ToASCII("xn--lwwp69lqs7m.xn--b7b605i") V6 +Pass ToASCII("᭄.᮪-≮≠") V6 +Pass ToASCII("᭄.᮪-≮≠") V6 +Pass ToASCII("᭄.᮪-≮≠") V6 +Pass ToASCII("᭄.᮪-≮≠") V6 +Pass ToASCII("xn--1uf.xn----nmlz65aub") V6 +Pass ToASCII("᯳Ⴑᅟ.U+dd34Ⅎ") V6 +Pass ToASCII("᯳Ⴑᅟ.U+dd34Ⅎ") V6 +Pass ToASCII("᯳ⴑᅟ.U+dd34ⅎ") V6 +Pass ToASCII("᯳Ⴑᅟ.U+dd34ⅎ") V6 +Pass ToASCII("xn--1zf224e.xn--73g3065g") V6 +Pass ToASCII("᯳ⴑᅟ.U+dd34ⅎ") V6 +Pass ToASCII("᯳Ⴑᅟ.U+dd34ⅎ") V6 +Pass ToASCII("xn--pnd26a55x.xn--73g3065g") V6; V7 +Pass ToASCII("xn--osd925cvyn.xn--73g3065g") V6; V7 +Pass ToASCII("xn--pnd26a55x.xn--f3g7465g") V6; V7 +Pass ToASCII("Ⴉ猕U+deeb≮.︒") V7 +Pass ToASCII("Ⴉ猕U+deeb≮.︒") V7 +Pass ToASCII("Ⴉ猕U+deeb≮.。") V7; A4_2 (ignored) +Pass ToASCII("Ⴉ猕U+deeb≮.。") V7; A4_2 (ignored) +Pass ToASCII("ⴉ猕U+deeb≮.。") V7; A4_2 (ignored) +Pass ToASCII("ⴉ猕U+deeb≮.。") V7; A4_2 (ignored) +Pass ToASCII("xn--gdh892bbz0d5438s..") V7; A4_2 (ignored) +Pass ToASCII("ⴉ猕U+deeb≮.︒") V7 +Pass ToASCII("ⴉ猕U+deeb≮.︒") V7 +Pass ToASCII("xn--gdh892bbz0d5438s.xn--y86c") V7 +Pass ToASCII("xn--hnd212gz32d54x5r..") V7; A4_2 (ignored) +Pass ToASCII("xn--hnd212gz32d54x5r.xn--y86c") V7 +Pass ToASCII("Å둄-.‌") C1; V3 (ignored) +Pass ToASCII("Å둄-.‌") C1; V3 (ignored) +Pass ToASCII("Å둄-.‌") C1; V3 (ignored) +Pass ToASCII("Å둄-.‌") C1; V3 (ignored) +Pass ToASCII("å둄-.‌") C1; V3 (ignored) +Pass ToASCII("å둄-.‌") C1; V3 (ignored) +Pass ToASCII("xn----1fa1788k.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----1fa1788k.xn--0ug") C1; V3 (ignored) +Pass ToASCII("å둄-.‌") C1; V3 (ignored) +Pass ToASCII("å둄-.‌") C1; V3 (ignored) +Pass ToASCII("룱‍U+df68‌。U+de16︒") C1; C2; V6; V7 +Pass ToASCII("룱‍U+df68‌。U+de16︒") C1; C2; V6; V7 +Pass ToASCII("룱‍U+df68‌。U+de16。") C1; C2; V6; A4_2 (ignored) +Pass ToASCII("룱‍U+df68‌。U+de16。") C1; C2; V6; A4_2 (ignored) +Pass ToASCII("xn--ct2b0738h.xn--772h.") V6; A4_2 (ignored) +Pass ToASCII("xn--0ugb3358ili2v.xn--772h.") C1; C2; V6; A4_2 (ignored) +Pass ToASCII("xn--ct2b0738h.xn--y86cl899a") V6; V7 +Pass ToASCII("xn--0ugb3358ili2v.xn--y86cl899a") C1; C2; V6; V7 +Pass ToASCII("U+dd04.᳜⒈ß") V6; V7; U1 (ignored) +Pass ToASCII("3,.᳜1.ß") V6; U1 (ignored) +Pass ToASCII("3,.᳜1.SS") V6; U1 (ignored) +Pass ToASCII("3,.᳜1.ss") V6; U1 (ignored) +Pass ToASCII("3,.᳜1.Ss") V6; U1 (ignored) +Pass ToASCII("3,.xn--1-43l.ss") V6; U1 (ignored) +Pass ToASCII("3,.xn--1-43l.xn--zca") V6; U1 (ignored) +Pass ToASCII("U+dd04.᳜⒈SS") V6; V7; U1 (ignored) +Pass ToASCII("U+dd04.᳜⒈ss") V6; V7; U1 (ignored) +Pass ToASCII("U+dd04.᳜⒈Ss") V6; V7; U1 (ignored) +Pass ToASCII("3,.xn--ss-k1r094b") V6; V7; U1 (ignored) +Pass ToASCII("3,.xn--zca344lmif") V6; V7; U1 (ignored) +Pass ToASCII("xn--x07h.xn--ss-k1r094b") V6; V7 +Pass ToASCII("xn--x07h.xn--zca344lmif") V6; V7 +Pass ToASCII("်्᷽.≠‍㇛") C2; V6 +Pass ToASCII("်्᷽.≠‍㇛") C2; V6 +Pass ToASCII("်्᷽.≠‍㇛") C2; V6 +Pass ToASCII("်्᷽.≠‍㇛") C2; V6 +Pass ToASCII("်्᷽.≠‍㇛") C2; V6 +Pass ToASCII("xn--n3b956a9zm.xn--1ch912d") V6 +Pass ToASCII("xn--n3b956a9zm.xn--1ug63gz5w") C2; V6 +Pass ToASCII("᯳.-逋U+ddadU+de6e") V6; V7; V3 (ignored) +Pass ToASCII("xn--1zf.xn----483d46987byr50b") V6; V7; V3 (ignored) +Pass ToASCII("xn--9ob.xn--4xa") +Pass ToASCII("ݖ.σ") +Pass ToASCII("ݖ.Σ") +Pass ToASCII("xn--9ob.xn--4xa380e") V7 +Pass ToASCII("xn--9ob.xn--4xa380ebol") C2; V7 +Pass ToASCII("xn--9ob.xn--3xa580ebol") C2; V7 +Pass ToASCII("xn--9ob.xn--4xa574u") V7 +Pass ToASCII("xn--9ob.xn--4xa795lq2l") C2; V7 +Pass ToASCII("xn--9ob.xn--3xa995lq2l") C2; V7 +Pass ToASCII("ᡆႣ。U+dca7̕‍‍") C2; V7 +Pass ToASCII("ᡆႣ。U+dca7̕‍‍") C2; V7 +Pass ToASCII("ᡆⴃ。U+dca7̕‍‍") C2; V7 +Pass ToASCII("xn--57e237h.xn--5sa98523p") V7 +Pass ToASCII("xn--57e237h.xn--5sa649la993427a") C2; V7 +Pass ToASCII("ᡆⴃ。U+dca7̕‍‍") C2; V7 +Pass ToASCII("xn--bnd320b.xn--5sa98523p") V7 +Pass ToASCII("xn--bnd320b.xn--5sa649la993427a") C2; V7 +Pass ToASCII("U+dc28。᭄U+dee8U+df87") V6; V7 +Pass ToASCII("U+dc28。᭄U+dee8U+df87") V6; V7 +Pass ToASCII("xn--mi4h.xn--1uf6843smg20c") V6; V7 +Pass ToASCII("ᢛU+dd5fß.ጧ") V7 +Pass ToASCII("ᢛU+dd5fSS.ጧ") V7 +Pass ToASCII("ᢛU+dd5fss.ጧ") V7 +Pass ToASCII("ᢛU+dd5fSs.ጧ") V7 +Pass ToASCII("xn--ss-7dp66033t.xn--p5d") V7 +Pass ToASCII("xn--zca562jc642x.xn--p5d") V7 +Pass ToASCII("⮒‌.U+de97‌") C1; V7 +Pass ToASCII("xn--b9i.xn--5p9y") V7 +Pass ToASCII("xn--0ugx66b.xn--0ugz2871c") C1; V7 +Pass ToASCII("≯U+df2bU+df47.᜴U+dfa4U+df6cᢧ") V6; V7 +Pass ToASCII("≯U+df2bU+df47.᜴U+dfa4U+df6cᢧ") V6; V7 +Pass ToASCII("xn--hdhx157g68o0g.xn--c0e65eu616c34o7a") V6; V7 +Pass ToASCII("ß。U+def3Ⴌྸ") +Pass ToASCII("ß。U+def3Ⴌྸ") +Pass ToASCII("ß。U+def3ⴌྸ") +Pass ToASCII("SS。U+def3Ⴌྸ") +Pass ToASCII("ss。U+def3ⴌྸ") +Pass ToASCII("Ss。U+def3Ⴌྸ") +Pass ToASCII("ss.xn--lgd921mvv0m") +Pass ToASCII("ss.U+def3ⴌྸ") +Pass ToASCII("SS.U+def3Ⴌྸ") +Pass ToASCII("Ss.U+def3Ⴌྸ") +Pass ToASCII("xn--zca.xn--lgd921mvv0m") +Pass ToASCII("ß.U+def3ⴌྸ") +Pass ToASCII("ß。U+def3ⴌྸ") +Pass ToASCII("SS。U+def3Ⴌྸ") +Pass ToASCII("ss。U+def3ⴌྸ") +Pass ToASCII("Ss。U+def3Ⴌྸ") +Pass ToASCII("ss.xn--lgd10cu829c") V7 +Pass ToASCII("xn--zca.xn--lgd10cu829c") V7 +Pass ToASCII("ᩚU+dd9d్。U+df6cU+dff5") V6; V7 +Pass ToASCII("ᩚU+dd9d్。U+df6c9") V6; V7 +Pass ToASCII("xn--lqc703ebm93a.xn--9-000p") V6; V7 +Pass ToASCII("ᡖ。̟U+dee8ஂ-") V6; V7; V3 (ignored) +Pass ToASCII("ᡖ。̟U+dee8ஂ-") V6; V7; V3 (ignored) +Pass ToASCII("xn--m8e.xn----mdb555dkk71m") V6; V7; V3 (ignored) +Pass ToASCII("֖Ⴋ.U+dff3≯︒︊") V6; V7 +Pass ToASCII("֖Ⴋ.U+dff3≯︒︊") V6; V7 +Pass ToASCII("֖Ⴋ.7≯。︊") V6; A4_2 (ignored) +Pass ToASCII("֖Ⴋ.7≯。︊") V6; A4_2 (ignored) +Pass ToASCII("֖ⴋ.7≯。︊") V6; A4_2 (ignored) +Pass ToASCII("֖ⴋ.7≯。︊") V6; A4_2 (ignored) +Pass ToASCII("xn--hcb613r.xn--7-pgo.") V6; A4_2 (ignored) +Pass ToASCII("֖ⴋ.U+dff3≯︒︊") V6; V7 +Pass ToASCII("֖ⴋ.U+dff3≯︒︊") V6; V7 +Pass ToASCII("xn--hcb613r.xn--7-pgoy530h") V6; V7 +Pass ToASCII("xn--hcb887c.xn--7-pgo.") V6; V7; A4_2 (ignored) +Pass ToASCII("xn--hcb887c.xn--7-pgoy530h") V6; V7 +Pass ToASCII("U+dd07伐︒.U+de5a꣄") V7; U1 (ignored) +Pass ToASCII("6,伐。.U+de5a꣄") V7; U1 (ignored); A4_2 (ignored) +Pass ToASCII("xn--6,-7i3c..xn--0f9ao925c") V7; U1 (ignored); A4_2 (ignored) +Pass ToASCII("xn--6,-7i3cj157d.xn--0f9ao925c") V7; U1 (ignored) +Pass ToASCII("xn--woqs083bel0g.xn--0f9ao925c") V7 +Pass ToASCII("U+dda0.U+dc34U+dfc8") V7; A4_2 (ignored) +Pass ToASCII("U+dda0.U+dc34U+dfc8") V7; A4_2 (ignored) +Pass ToASCII(".xn--rx21bhv12i") V7; A4_2 (ignored) +Pass ToASCII("-.ᢆU+dca3-") V6; V7; V3 (ignored) +Pass ToASCII("-.xn----pbkx6497q") V6; V7; V3 (ignored) +Pass ToASCII("U+dcb0.-U+dffbß") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-5ß") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-5SS") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-5ss") V7; V3 (ignored) +Pass ToASCII("xn--t960e.-5ss") V7; V3 (ignored) +Pass ToASCII("xn--t960e.xn---5-hia") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-U+dffbSS") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-U+dffbss") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-U+dffbSs") V7; V3 (ignored) +Pass ToASCII("U+dcb0.-5Ss") V7; V3 (ignored) +Pass ToASCII("‍U+de3f.U+dd12ჅU+dfb6") C2; V7 +Pass ToASCII("‍U+de3f.U+dd12ⴥU+dfb6") C2; V7 +Pass ToASCII("xn--0s9c.xn--tljz038l0gz4b") V6; V7 +Pass ToASCII("xn--1ug9533g.xn--tljz038l0gz4b") C2; V7 +Pass ToASCII("xn--0s9c.xn--9nd3211w0gz4b") V6; V7 +Pass ToASCII("xn--1ug9533g.xn--9nd3211w0gz4b") C2; V7 +Pass ToASCII("U+dec5。ßU+dd69‍") C2; V7 +Pass ToASCII("U+dec5。SSU+dd69‍") C2; V7 +Pass ToASCII("U+dec5。ssU+dd69‍") C2; V7 +Pass ToASCII("U+dec5。SsU+dd69‍") C2; V7 +Pass ToASCII("xn--ey1p.xn--ss-eq36b") V7 +Pass ToASCII("xn--ey1p.xn--ss-n1tx0508a") C2; V7 +Pass ToASCII("xn--ey1p.xn--zca870nz438b") C2; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻ς≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻ς≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻ς≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻ς≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻Σ≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻Σ≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻σ≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻σ≠") C1; V6; V7 +Pass ToASCII("xn--zb9h5968x.xn--4xa378i1mfjw7y") V6; V7 +Pass ToASCII("xn--0ug3766p5nm1b.xn--4xa378i1mfjw7y") C1; V6; V7 +Pass ToASCII("xn--0ug3766p5nm1b.xn--3xa578i1mfjw7y") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻Σ≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻Σ≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻σ≠") C1; V6; V7 +Pass ToASCII("U+dd6fU+df6d‌U+df2d。U+ddbf᪻σ≠") C1; V6; V7 +Pass ToASCII("⒋。⒈‍U+dd22") C2; V7 +Pass ToASCII("4.。1.‍U+dd22") C2; V7; A4_2 (ignored) +Pass ToASCII("4..1.xn--sf51d") V7; A4_2 (ignored) +Pass ToASCII("4..1.xn--1ug64613i") C2; V7; A4_2 (ignored) +Pass ToASCII("xn--wsh.xn--tsh07994h") V7 +Pass ToASCII("xn--wsh.xn--1ug58o74922a") C2; V7 +Pass ToASCII("ႳU+df2b‍U+df53.ڧU+dc36") V7 +Pass ToASCII("ႳU+df2b‍U+df53.ڧU+dc36") V7 +Pass ToASCII("ⴓU+df2b‍U+df53.ڧU+dc36") V7 +Pass ToASCII("xn--blj6306ey091d.xn--9jb4223l") V7 +Pass ToASCII("xn--1ugy52cym7p7xu5e.xn--9jb4223l") V7 +Pass ToASCII("ⴓU+df2b‍U+df53.ڧU+dc36") V7 +Pass ToASCII("xn--rnd8945ky009c.xn--9jb4223l") V7 +Pass ToASCII("xn--rnd479ep20q7x12e.xn--9jb4223l") V7 +Pass ToASCII("U+de3f.U+dd06—") V6; U1 (ignored) +Pass ToASCII("U+de3f.5,—") V6; U1 (ignored) +Pass ToASCII("xn--0s9c.xn--5,-81t") V6; U1 (ignored) +Pass ToASCII("xn--0s9c.xn--8ug8324p") V6; V7 +Pass ToASCII("U+deb1U+ddae۸。U+dfad-") V7; V3 (ignored) +Pass ToASCII("xn--lmb18944c0g2z.xn----2k81m") V7; V3 (ignored) +Pass ToASCII("U+df85U+dce1U+df59.U+ddb7") V7 +Pass ToASCII("xn--ie9hi1349bqdlb.xn--oj69a") V7 +Pass ToASCII("⃧U+dc4e-U+dcdd.4Ⴄ‌") C1; V6; V7 +Pass ToASCII("⃧U+dc4e-U+dcdd.4ⴄ‌") C1; V6; V7 +Pass ToASCII("xn----9snu5320fi76w.xn--4-ivs") V6; V7 +Pass ToASCII("xn----9snu5320fi76w.xn--4-sgn589c") C1; V6; V7 +Pass ToASCII("xn----9snu5320fi76w.xn--4-f0g") V6; V7 +Pass ToASCII("xn----9snu5320fi76w.xn--4-f0g649i") C1; V6; V7 +Pass ToASCII("ᚭ。U+df20ßU+def1") +Pass ToASCII("ᚭ。U+df20ßU+def1") +Pass ToASCII("ᚭ。U+df20SSU+def1") +Pass ToASCII("ᚭ。U+df20ssU+def1") +Pass ToASCII("ᚭ。U+df20SsU+def1") +Pass ToASCII("xn--hwe.xn--ss-ci1ub261a") +Pass ToASCII("ᚭ.U+df20ssU+def1") +Pass ToASCII("ᚭ.U+df20SSU+def1") +Pass ToASCII("ᚭ.U+df20SsU+def1") +Pass ToASCII("xn--hwe.xn--zca4946pblnc") +Pass ToASCII("ᚭ.U+df20ßU+def1") +Pass ToASCII("ᚭ。U+df20SSU+def1") +Pass ToASCII("ᚭ。U+df20ssU+def1") +Pass ToASCII("ᚭ。U+df20SsU+def1") +Pass ToASCII("U+dc44≯。U+df24") V6 +Pass ToASCII("U+dc44≯。U+df24") V6 +Pass ToASCII("U+dc44≯。U+df24") V6 +Pass ToASCII("U+dc44≯。U+df24") V6 +Pass ToASCII("xn--hdh5636g.xn--ci2d") V6 +Pass ToASCII("Ⴋ≮U+dc86。‍ާU+dee3") C2 +Pass ToASCII("Ⴋ≮U+dc86。‍ާU+dee3") C2 +Pass ToASCII("ⴋ≮U+dc86。‍ާU+dee3") C2 +Pass ToASCII("ⴋ≮U+dc86。‍ާU+dee3") C2 +Pass ToASCII("xn--gdhz03bxt42d.xn--lrb6479j") V6 +Pass ToASCII("xn--gdhz03bxt42d.xn--lrb506jqr4n") C2 +Pass ToASCII("xn--jnd802gsm17c.xn--lrb6479j") V6; V7 +Pass ToASCII("xn--jnd802gsm17c.xn--lrb506jqr4n") C2; V7 +Pass ToASCII("្.U+df52≯") V6; V7 +Pass ToASCII("្.U+df52≯") V6; V7 +Pass ToASCII("xn--u4e.xn--hdhx0084f") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aÉ⬓U+dd34") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aÉ⬓U+dd34") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aÉ⬓U+dd34") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aÉ⬓U+dd34") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aé⬓U+dd34") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aé⬓U+dd34") V6; V7 +Pass ToASCII("xn--c0e34564d.xn--9ca207st53lg3f") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aé⬓U+dd34") V6; V7 +Pass ToASCII("U+dc47᜴.U+de3aé⬓U+dd34") V6; V7 +Pass ToASCII("xn--09e4694e..xn--ye6h") A4_2 (ignored) +Pass ToASCII("Ⴣ.ٓᢤ") V6 +Pass ToASCII("Ⴣ.ٓᢤ") V6 +Pass ToASCII("ⴣ.ٓᢤ") V6 +Pass ToASCII("xn--rlj.xn--vhb294g") V6 +Pass ToASCII("ⴣ.ٓᢤ") V6 +Pass ToASCII("xn--7nd.xn--vhb294g") V6; V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbჄU+dc50") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbჄU+dc50") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbჄU+dc50") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbჄU+dc50") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbⴤU+dc50") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbⴤU+dc50") V7 +Pass ToASCII("xn--oub.xn--sljz109bpe25dviva") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbⴤU+dc50") V7 +Pass ToASCII("U+dd08ࠓ.싉U+ddbbⴤU+dc50") V7 +Pass ToASCII("xn--oub.xn--8nd9522gpe69cviva") V7 +Pass ToASCII("ꨬU+dcab≮.⤂") V6 +Pass ToASCII("ꨬU+dcab≮.⤂") V6 +Pass ToASCII("ꨬU+dcab≮.⤂") V6 +Pass ToASCII("ꨬU+dcab≮.⤂") V6 +Pass ToASCII("xn--gdh1854cn19c.xn--kqi") V6 +Pass ToASCII("U+dc45。-") V6; V3 (ignored) +Pass ToASCII("xn--210d.-") V6; V3 (ignored) +Pass ToASCII("ꡦᡑ‍⒈。U+dee3-") C2; V7; V3 (ignored) +Pass ToASCII("ꡦᡑ‍1.。U+dee3-") C2; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--1-o7j0610f..xn----381i") V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--1-o7j663bdl7m..xn----381i") C2; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--h8e863drj7h.xn----381i") V7; V3 (ignored) +Pass ToASCII("xn--h8e470bl0d838o.xn----381i") C2; V7; V3 (ignored) +Pass ToASCII("⒈䰹‍-。웈") C2; V7; V3 (ignored) +Pass ToASCII("⒈䰹‍-。웈") C2; V7; V3 (ignored) +Pass ToASCII("1.䰹‍-。웈") C2; V3 (ignored) +Pass ToASCII("1.䰹‍-。웈") C2; V3 (ignored) +Pass ToASCII("1.xn----zw5a.xn--kp5b") V3 (ignored) +Pass ToASCII("1.xn----tgnz80r.xn--kp5b") C2; V3 (ignored) +Pass ToASCII("xn----dcp160o.xn--kp5b") V7; V3 (ignored) +Pass ToASCII("xn----tgnx5rjr6c.xn--kp5b") C2; V7; V3 (ignored) +Pass ToASCII("て。‌U+dcfd߳") C1; V7 +Pass ToASCII("xn--m9j.xn--rtb10784p") V7 +Pass ToASCII("xn--m9j.xn--rtb154j9l73w") C1; V7 +Pass ToASCII("ς。꧀ۧ") V6 +Pass ToASCII("ς。꧀ۧ") V6 +Pass ToASCII("Σ。꧀ۧ") V6 +Pass ToASCII("σ。꧀ۧ") V6 +Pass ToASCII("xn--4xa.xn--3lb1944f") V6 +Pass ToASCII("xn--3xa.xn--3lb1944f") V6 +Pass ToASCII("Σ。꧀ۧ") V6 +Pass ToASCII("σ。꧀ۧ") V6 +Pass ToASCII("்U+dec5U+de51.ႢႵ") V6; V7 +Pass ToASCII("்U+dec5U+de51.ⴂⴕ") V6; V7 +Pass ToASCII("்U+dec5U+de51.Ⴂⴕ") V6; V7 +Pass ToASCII("xn--xmc83135idcxza.xn--tkjwb") V6; V7 +Pass ToASCII("xn--xmc83135idcxza.xn--9md086l") V6; V7 +Pass ToASCII("xn--xmc83135idcxza.xn--9md2b") V6; V7 +Pass ToASCII("ᰲU+dd08⾛֦.‍U+dd64߽") C2; V6; V7; U1 (ignored) +Pass ToASCII("ᰲ7,走֦.‍U+dd64߽") C2; V6; V7; U1 (ignored) +Pass ToASCII("xn--7,-bid991urn3k.xn--1tb13454l") V6; V7; U1 (ignored) +Pass ToASCII("xn--7,-bid991urn3k.xn--1tb334j1197q") C2; V6; V7; U1 (ignored) +Pass ToASCII("xn--xcb756i493fwi5o.xn--1tb13454l") V6; V7 +Pass ToASCII("xn--xcb756i493fwi5o.xn--1tb334j1197q") C2; V6; V7 +Pass ToASCII("ᢗ。ӀU+dd3b") V7 +Pass ToASCII("ᢗ。ӀU+dd3b") V7 +Pass ToASCII("ᢗ。ӏU+dd3b") V7 +Pass ToASCII("xn--hbf.xn--s5a83117e") V7 +Pass ToASCII("ᢗ。ӏU+dd3b") V7 +Pass ToASCII("xn--hbf.xn--d5a86117e") V7 +Pass ToASCII("-U+def7U+df91。U+ddac") V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----991iq40y.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("U+dc98U+dd12U+dc61。U+dfeaႼ") V6 +Pass ToASCII("U+dc98U+dd12U+dc61。8Ⴜ") V6 +Pass ToASCII("U+dc98U+dd12U+dc61。8ⴜ") V6 +Pass ToASCII("xn--7m3d291b.xn--8-vws") V6 +Pass ToASCII("U+dc98U+dd12U+dc61。U+dfeaⴜ") V6 +Pass ToASCII("xn--7m3d291b.xn--8-s1g") V6; V7 +Pass ToASCII("᮫。U+dc89U+dc70") V6; V7 +Pass ToASCII("᮫。U+dc89U+dc70") V6; V7 +Pass ToASCII("xn--zxf.xn--fx7ho0250c") V6; V7 +Pass ToASCII("U+deb6U+ded6U+de70-。‌") C1; V7; V3 (ignored) +Pass ToASCII("xn----7i12hu122k9ire.") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----7i12hu122k9ire.xn--0ug") C1; V7; V3 (ignored) +Pass ToASCII("︒.︯U+dc42") V6; V7 +Pass ToASCII("︒.U+dc42︯") V6; V7 +Pass ToASCII("。.U+dc42︯") V6; A4_2 (ignored) +Pass ToASCII("..xn--s96cu30b") V6; A4_2 (ignored) +Pass ToASCII("xn--y86c.xn--s96cu30b") V6; V7 +Pass ToASCII("꤬。‍") C2; V6 +Pass ToASCII("xn--zi9a.") V6; A4_2 (ignored) +Pass ToASCII("xn--zi9a.xn--1ug") C2; V6 +Pass ToASCII("U+de04。-") V7; V3 (ignored) +Pass ToASCII("xn--xm38e.-") V7; V3 (ignored) +Pass ToASCII("⋠U+deee.U+de2e༘ß≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ß≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ß≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ß≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘SS≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘SS≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ss≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ss≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘Ss≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘Ss≯") V7 +Pass ToASCII("xn--pgh4639f.xn--ss-ifj426nle504a") V7 +Pass ToASCII("xn--pgh4639f.xn--zca593eo6oc013y") V7 +Pass ToASCII("⋠U+deee.U+de2e༘SS≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘SS≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ss≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘ss≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘Ss≯") V7 +Pass ToASCII("⋠U+deee.U+de2e༘Ss≯") V7 +Pass ToASCII("̰.U+df31蚀") V6; V7 +Pass ToASCII("̰.U+df31蚀") V6; V7 +Pass ToASCII("xn--xta.xn--e91aw9417e") V6; V7 +Pass ToASCII("U+dc9fU+dd08‍ꡎ。྄") C2; V6; U1 (ignored) +Pass ToASCII("U+dc9f7,‍ꡎ。྄") C2; V6; U1 (ignored) +Pass ToASCII("xn--7,-gh9hg322i.xn--3ed") V6; U1 (ignored) +Pass ToASCII("xn--7,-n1t0654eqo3o.xn--3ed") C2; V6; U1 (ignored) +Pass ToASCII("xn--nc9aq743ds0e.xn--3ed") V6; V7 +Pass ToASCII("xn--1ug4874cfd0kbmg.xn--3ed") C2; V6; V7 +Pass ToASCII("ꡔ。္ᢇ") V6 +Pass ToASCII("xn--tc9a.xn--9jd663b") V6 +Pass ToASCII("⃫≮.U+de16") V6 +Pass ToASCII("⃫≮.U+de16") V6 +Pass ToASCII("xn--e1g71d.xn--772h") V6 +Pass ToASCII("‌.≯") C1 +Pass ToASCII("‌.≯") C1 +Pass ToASCII(".xn--hdh") A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--hdh") C1 +Pass ToASCII("U+dd67U+de60-.꯭-悜") V6; V7; V3 (ignored) +Pass ToASCII("U+dd67U+de60-.꯭-悜") V6; V7; V3 (ignored) +Pass ToASCII("xn----7m53aj640l.xn----8f4br83t") V6; V7; V3 (ignored) +Pass ToASCII("ᡉU+dce7⬞ᢜ.-‍U+dcd1‮") C2; V7; V3 (ignored) +Pass ToASCII("xn--87e0ol04cdl39e.xn----qinu247r") V7; V3 (ignored) +Pass ToASCII("xn--87e0ol04cdl39e.xn----ugn5e3763s") C2; V7; V3 (ignored) +Pass ToASCII("U+dd53.ܘ") +Pass ToASCII("U+dd53.ܘ") +Pass ToASCII("xn--of6h.xn--inb") +Pass ToASCII("U+dd3d-.-්") V3 (ignored) +Pass ToASCII("U+dd3d-.-්") V3 (ignored) +Pass ToASCII("-.xn----ptf") V3 (ignored) +Pass ToASCII("ႺU+def8U+dd04。U+dfddퟶ်") +Pass ToASCII("ႺU+def8U+dd04。5ퟶ်") +Pass ToASCII("ⴚU+def8U+dd04。5ퟶ်") +Pass ToASCII("xn--ilj2659d.xn--5-dug9054m") +Pass ToASCII("ⴚU+def8.5ퟶ်") +Pass ToASCII("ႺU+def8.5ퟶ်") +Pass ToASCII("ⴚU+def8U+dd04。U+dfddퟶ်") +Pass ToASCII("xn--ynd2415j.xn--5-dug9054m") V7 +Pass ToASCII("‍-ᠹ﹪.ᷡᤢ") C2; V6; U1 (ignored) +Pass ToASCII("‍-ᠹ%.ᷡᤢ") C2; V6; U1 (ignored) +Pass ToASCII("xn---%-u4o.xn--gff52t") V6; V3 (ignored); U1 (ignored) +Pass ToASCII("xn---%-u4oy48b.xn--gff52t") C2; V6; U1 (ignored) +Pass ToASCII("xn----c6jx047j.xn--gff52t") V6; V7; V3 (ignored) +Pass ToASCII("xn----c6j614b1z4v.xn--gff52t") C2; V6; V7 +Pass ToASCII("≠.ᠿ") +Pass ToASCII("≠.ᠿ") +Pass ToASCII("xn--1ch.xn--y7e") +Pass ToASCII("ܣ֣。㌪") +Pass ToASCII("ܣ֣。ハイツ") +Pass ToASCII("xn--ucb18e.xn--eck4c5a") +Pass ToASCII("ܣ֣.ハイツ") +Pass ToASCII("U+de6b.U+dc72") V7 +Pass ToASCII("U+de6b.U+dc72") V7 +Pass ToASCII("xn--td3j.xn--4628b") V7 +Pass ToASCII("xn--skb") +Pass ToASCII("ڹ") +Pass ToASCII("్U+de3e֩U+dfed。-U+df28") V6; V3 (ignored) +Pass ToASCII("్U+de3e֩1。-U+df28") V6; V3 (ignored) +Pass ToASCII("xn--1-rfc312cdp45c.xn----nq0j") V6; V3 (ignored) +Pass ToASCII("U+dfc8。뙏") V7 +Pass ToASCII("U+dfc8。뙏") V7 +Pass ToASCII("xn--ph26c.xn--281b") V7 +Pass ToASCII("U+de1aU+dd0cU+df40ᡀ.ࢶ") V7 +Pass ToASCII("xn--z7e98100evc01b.xn--czb") V7 +Pass ToASCII("‍。U+dc5b") C2; V7 +Pass ToASCII("‍。U+dc5b") C2; V7 +Pass ToASCII(".xn--6x4u") V7; A4_2 (ignored) +Pass ToASCII("xn--1ug.xn--6x4u") C2; V7 +Pass ToASCII("‌。曳⾑U+def0≯") C1; V7 +Pass ToASCII("‌。曳⾑U+def0≯") C1; V7 +Pass ToASCII("‌。曳襾U+def0≯") C1; V7 +Pass ToASCII("‌。曳襾U+def0≯") C1; V7 +Pass ToASCII("xn--vn7c.xn--hdh501y8wvfs5h") V7 +Pass ToASCII("xn--0ug2139f.xn--hdh501y8wvfs5h") C1; V7 +Pass ToASCII("≯⒈。ß") V7 +Pass ToASCII("≯⒈。ß") V7 +Pass ToASCII("≯1.。ß") A4_2 (ignored) +Pass ToASCII("≯1.。ß") A4_2 (ignored) +Pass ToASCII("≯1.。SS") A4_2 (ignored) +Pass ToASCII("≯1.。SS") A4_2 (ignored) +Pass ToASCII("≯1.。ss") A4_2 (ignored) +Pass ToASCII("≯1.。ss") A4_2 (ignored) +Pass ToASCII("≯1.。Ss") A4_2 (ignored) +Pass ToASCII("≯1.。Ss") A4_2 (ignored) +Pass ToASCII("xn--1-ogo..ss") A4_2 (ignored) +Pass ToASCII("xn--1-ogo..xn--zca") A4_2 (ignored) +Pass ToASCII("≯⒈。SS") V7 +Pass ToASCII("≯⒈。SS") V7 +Pass ToASCII("≯⒈。ss") V7 +Pass ToASCII("≯⒈。ss") V7 +Pass ToASCII("≯⒈。Ss") V7 +Pass ToASCII("≯⒈。Ss") V7 +Pass ToASCII("xn--hdh84f.ss") V7 +Pass ToASCII("xn--hdh84f.xn--zca") V7 +Pass ToASCII("‌。≠") C1 +Pass ToASCII("‌。≠") C1 +Pass ToASCII("‌。≠") C1 +Pass ToASCII("‌。≠") C1 +Pass ToASCII(".xn--1ch") A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--1ch") C1 +Pass ToASCII("U+ddbfU+de14.ᡟU+ddbfᭂ‌") C1; V6 +Pass ToASCII("xn--461dw464a.xn--v8e29loy65a") V6 +Pass ToASCII("xn--461dw464a.xn--v8e29ldzfo952a") C1; V6 +Pass ToASCII("U+dcf3‍U+df71.U+df34Ↄ≠-") C2; V6; V7; V3 (ignored) +Pass ToASCII("U+dcf3‍U+df71.U+df34Ↄ≠-") C2; V6; V7; V3 (ignored) +Pass ToASCII("U+dcf3‍U+df71.U+df34ↄ≠-") C2; V6; V7; V3 (ignored) +Pass ToASCII("U+dcf3‍U+df71.U+df34ↄ≠-") C2; V6; V7; V3 (ignored) +Pass ToASCII("xn--6j00chy9a.xn----81n51bt713h") V6; V7; V3 (ignored) +Pass ToASCII("xn--1ug15151gkb5a.xn----81n51bt713h") C2; V6; V7; V3 (ignored) +Pass ToASCII("xn--6j00chy9a.xn----61n81bt713h") V6; V7; V3 (ignored) +Pass ToASCII("xn--1ug15151gkb5a.xn----61n81bt713h") C2; V6; V7; V3 (ignored) +Pass ToASCII("‍┮U+ddd0.ఀ్᜴‍") C2; V6 +Pass ToASCII("‍┮U+ddd0.ఀ్᜴‍") C2; V6 +Pass ToASCII("xn--kxh.xn--eoc8m432a") V6 +Pass ToASCII("xn--1ug04r.xn--eoc8m432a40i") C2; V6 +Pass ToASCII("U+deaa。U+dd02") V7; U1 (ignored) +Pass ToASCII("U+deaa。1,") V7; U1 (ignored) +Pass ToASCII("xn--n433d.1,") V7; U1 (ignored) +Pass ToASCII("xn--n433d.xn--v07h") V7 +Pass ToASCII("U+df68刍.U+dee6") V6 +Pass ToASCII("xn--rbry728b.xn--y88h") V6 +Pass ToASCII("U+df0f3。ᯱU+dfd2") V6; V7 +Pass ToASCII("U+df0f3。ᯱ4") V6; V7 +Pass ToASCII("xn--3-ib31m.xn--4-pql") V6; V7 +Pass ToASCII("꡽≯.U+dc80U+dcc4") V7 +Pass ToASCII("꡽≯.U+dc80U+dcc4") V7 +Pass ToASCII("꡽≯.U+dc80U+dcc4") V7 +Pass ToASCII("꡽≯.U+dc80U+dcc4") V7 +Pass ToASCII("xn--hdh8193c.xn--5z40cp629b") V7 +Pass ToASCII("U+dcdb.‍䤫≠Ⴞ") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠Ⴞ") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠Ⴞ") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠Ⴞ") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠ⴞ") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠ⴞ") C2; V7 +Pass ToASCII("xn--1t56e.xn--1ch153bqvw") V7 +Pass ToASCII("xn--1t56e.xn--1ug73gzzpwi3a") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠ⴞ") C2; V7 +Pass ToASCII("U+dcdb.‍䤫≠ⴞ") C2; V7 +Pass ToASCII("xn--1t56e.xn--2nd141ghl2a") V7 +Pass ToASCII("xn--1t56e.xn--2nd159e9vb743e") C2; V7 +Pass ToASCII("3.1.xn--110d.j") V6 +Pass ToASCII("xn--tshd3512p.j") V7 +Pass ToASCII("͊.U+de0e") V6 +Pass ToASCII("͊.U+de0e") V6 +Pass ToASCII("xn--oua.xn--mr9c") V6 +Pass ToASCII("훉≮。ิ") V6 +Pass ToASCII("훉≮。ิ") V6 +Pass ToASCII("훉≮。ิ") V6 +Pass ToASCII("훉≮。ิ") V6 +Pass ToASCII("xn--gdh2512e.xn--i4c") V6 +Pass ToASCII("ꡆ。Ↄྵ놮-") V3 (ignored) +Pass ToASCII("ꡆ。Ↄྵ놮-") V3 (ignored) +Pass ToASCII("ꡆ。ↄྵ놮-") V3 (ignored) +Pass ToASCII("ꡆ。ↄྵ놮-") V3 (ignored) +Pass ToASCII("xn--fc9a.xn----qmg097k469k") V3 (ignored) +Pass ToASCII("xn--fc9a.xn----qmg787k869k") V7; V3 (ignored) +Pass ToASCII("≮U+dd76.U+dc81ꫬ⹈U+dd6d") V7 +Pass ToASCII("≮U+dd76.U+dc81ꫬ⹈U+dd6d") V7 +Pass ToASCII("≮U+dd76.U+dc81ꫬ⹈U+dd6d") V7 +Pass ToASCII("≮U+dd76.U+dc81ꫬ⹈U+dd6d") V7 +Pass ToASCII("xn--gdh.xn--4tjx101bsg00ds9pyc") V7 +Pass ToASCII("xn--gdh0880o.xn--4tjx101bsg00ds9pyc") V7 +Pass ToASCII("U+dc42。‍U+df80U+df95U+dc54") C2; V6; V7 +Pass ToASCII("U+dc42。‍U+df80U+df95U+dc54") C2; V6; V7 +Pass ToASCII("xn--8v1d.xn--ye9h41035a2qqs") V6; V7 +Pass ToASCII("xn--8v1d.xn--1ug1386plvx1cd8vya") C2; V6; V7 +Pass ToASCII("ßুᷭ。ؠ8₅") +Pass ToASCII("ßুᷭ。ؠ85") +Pass ToASCII("SSুᷭ。ؠ85") +Pass ToASCII("ssুᷭ。ؠ85") +Pass ToASCII("Ssুᷭ。ؠ85") +Pass ToASCII("xn--ss-e2f077r.xn--85-psd") +Pass ToASCII("ssুᷭ.ؠ85") +Pass ToASCII("SSুᷭ.ؠ85") +Pass ToASCII("Ssুᷭ.ؠ85") +Pass ToASCII("xn--zca266bwrr.xn--85-psd") +Pass ToASCII("ßুᷭ.ؠ85") +Pass ToASCII("SSুᷭ。ؠ8₅") +Pass ToASCII("ssুᷭ。ؠ8₅") +Pass ToASCII("Ssুᷭ。ؠ8₅") +Pass ToASCII("︍છ。嵨") +Pass ToASCII("xn--6dc.xn--tot") +Pass ToASCII("છ.嵨") +Pass ToASCII("-‌⒙U+dee5。U+de35") C1; V6; V7; V3 (ignored) +Pass ToASCII("-‌18.U+dee5。U+de35") C1; V6; V3 (ignored) +Pass ToASCII("-18.xn--rx9c.xn--382h") V6; V3 (ignored) +Pass ToASCII("xn---18-9m0a.xn--rx9c.xn--382h") C1; V6; V3 (ignored) +Pass ToASCII("xn----ddps939g.xn--382h") V6; V7; V3 (ignored) +Pass ToASCII("xn----sgn18r3191a.xn--382h") C1; V6; V7; V3 (ignored) +Pass ToASCII("︅︒。U+dc3e᳠") V7 +Pass ToASCII("︅。。U+dc3e᳠") A4_2 (ignored) +Pass ToASCII("..xn--t6f5138v") A4_2 (ignored) +Pass ToASCII("xn--y86c.xn--t6f5138v") V7 +Pass ToASCII("xn--t6f5138v") +Pass ToASCII("U+dc3e᳠") +Pass ToASCII("U+dd4f.-ß‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ß‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ß‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ß‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-SS‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-SS‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-Ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-Ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("xn--u836e.xn---ss-gl2a") V7; V3 (ignored) +Pass ToASCII("xn--u836e.xn---ss-cn0at5l") C1; V7; V3 (ignored) +Pass ToASCII("xn--u836e.xn----qfa750ve7b") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-SS‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-SS‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-Ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("U+dd4f.-Ss‌≠") C1; V7; V3 (ignored) +Pass ToASCII("ᡙ‌。≯U+def2≠") C1 +Pass ToASCII("ᡙ‌。≯U+def2≠") C1 +Pass ToASCII("ᡙ‌。≯U+def2≠") C1 +Pass ToASCII("ᡙ‌。≯U+def2≠") C1 +Pass ToASCII("xn--p8e.xn--1ch3a7084l") +Pass ToASCII("ᡙ.≯U+def2≠") +Pass ToASCII("ᡙ.≯U+def2≠") +Pass ToASCII("xn--p8e650b.xn--1ch3a7084l") C1 +Pass ToASCII("U+dd5bؓ.Ⴕ") V7 +Pass ToASCII("U+dd5bؓ.ⴕ") V7 +Pass ToASCII("xn--1fb94204l.xn--dlj") V7 +Pass ToASCII("xn--1fb94204l.xn--tnd") V7 +Pass ToASCII("‌U+dd37。U+dc41") C1; V7 +Pass ToASCII("‌U+dd37。U+dc41") C1; V7 +Pass ToASCII(".xn--w720c") V7; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--w720c") C1; V7 +Pass ToASCII("⒈ූ焅.U+dc59‍ꡟ") C2; V7 +Pass ToASCII("1.ූ焅.U+dc59‍ꡟ") C2; V6; V7 +Pass ToASCII("1.xn--t1c6981c.xn--4c9a21133d") V6; V7 +Pass ToASCII("1.xn--t1c6981c.xn--1ugz184c9lw7i") C2; V6; V7 +Pass ToASCII("xn--t1c337io97c.xn--4c9a21133d") V7 +Pass ToASCII("xn--t1c337io97c.xn--1ugz184c9lw7i") C2; V7 +Pass ToASCII("U+ddc0▍.⁞ᠰ") V6 +Pass ToASCII("xn--9zh3057f.xn--j7e103b") V6 +Pass ToASCII("-3.‍ヌᢕ") C2; V3 (ignored) +Pass ToASCII("-3.xn--fbf115j") V3 (ignored) +Pass ToASCII("-3.xn--fbf739aq5o") C2; V3 (ignored) +Pass ToASCII("Ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("Ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("Ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("Ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("xn--u4e969b.xn--1ch") +Pass ToASCII("ⅎ្.≠") +Pass ToASCII("ⅎ្.≠") +Pass ToASCII("Ⅎ្.≠") +Pass ToASCII("Ⅎ្.≠") +Pass ToASCII("xn--u4e823bq1a.xn--0ugb89o") C1; C2 +Pass ToASCII("ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("ⅎ្‍。≠‍‌") C1; C2 +Pass ToASCII("xn--u4e319b.xn--1ch") V7 +Pass ToASCII("xn--u4e823bcza.xn--0ugb89o") C1; C2; V7 +Pass ToASCII("U+dd2fྨ.≯") V7 +Pass ToASCII("U+dd2fྨ.≯") V7 +Pass ToASCII("U+dd2fྨ.≯") V7 +Pass ToASCII("U+dd2fྨ.≯") V7 +Pass ToASCII("xn--4fd57150h.xn--hdh") V7 +Pass ToASCII("U+de3fU+dd8c鸮U+deb6.ς") V6 +Pass ToASCII("U+de3fU+dd8c鸮U+deb6.Σ") V6 +Pass ToASCII("U+de3fU+dd8c鸮U+deb6.σ") V6 +Pass ToASCII("xn--l76a726rt2h.xn--4xa") V6 +Pass ToASCII("xn--l76a726rt2h.xn--3xa") V6 +Pass ToASCII("ς-。‌U+dfed-") C1; V3 (ignored) +Pass ToASCII("ς-。‌1-") C1; V3 (ignored) +Pass ToASCII("Σ-。‌1-") C1; V3 (ignored) +Pass ToASCII("σ-。‌1-") C1; V3 (ignored) +Pass ToASCII("xn----zmb.1-") V3 (ignored) +Pass ToASCII("xn----zmb.xn--1--i1t") C1; V3 (ignored) +Pass ToASCII("xn----xmb.xn--1--i1t") C1; V3 (ignored) +Pass ToASCII("Σ-。‌U+dfed-") C1; V3 (ignored) +Pass ToASCII("σ-。‌U+dfed-") C1; V3 (ignored) +Pass ToASCII("᜴-ೢ.U+dd29Ⴄ") V6 +Pass ToASCII("᜴-ೢ.U+dd29Ⴄ") V6 +Pass ToASCII("᜴-ೢ.U+dd29ⴄ") V6 +Pass ToASCII("xn----ggf830f.xn--vkj") V6 +Pass ToASCII("᜴-ೢ.U+dd29ⴄ") V6 +Pass ToASCII("xn----ggf830f.xn--cnd") V6; V7 +Pass ToASCII("‍。U+dc18⒈ꡍ擉") C2; V6; V7 +Pass ToASCII("‍。U+dc181.ꡍ擉") C2; V6 +Pass ToASCII(".xn--1-1p4r.xn--s7uv61m") V6; A4_2 (ignored) +Pass ToASCII("xn--1ug.xn--1-1p4r.xn--s7uv61m") C2; V6 +Pass ToASCII(".xn--tsh026uql4bew9p") V6; V7; A4_2 (ignored) +Pass ToASCII("xn--1ug.xn--tsh026uql4bew9p") C2; V6; V7 +Pass ToASCII("⫐。Ⴠ-U+dc22") V7 +Pass ToASCII("⫐。Ⴠ-U+dc22") V7 +Pass ToASCII("⫐。ⴠ-U+dc22") V7 +Pass ToASCII("xn--r3i.xn----2wst7439i") V7 +Pass ToASCII("⫐。ⴠ-U+dc22") V7 +Pass ToASCII("xn--r3i.xn----z1g58579u") V7 +Pass ToASCII("U+dc42◊.⦟∠") V6 +Pass ToASCII("U+dc42◊.⦟∠") V6 +Pass ToASCII("xn--01h3338f.xn--79g270a") V6 +Pass ToASCII("헁U+dd99ฺU+df5a。ںU+dfdc") V7 +Pass ToASCII("헁U+dd99ฺU+df5a。ںU+dfdc") V7 +Pass ToASCII("헁U+dd99ฺU+df5a。ں4") V7 +Pass ToASCII("헁U+dd99ฺU+df5a。ں4") V7 +Pass ToASCII("xn--o4c1723h8g85gt4ya.xn--4-dvc") V7 +Pass ToASCII("꥓.̽U+dcbd馋") V6; V7 +Pass ToASCII("xn--3j9a.xn--bua0708eqzrd") V6; V7 +Pass ToASCII("U+deddU+def8‍。䜖") C2; V7 +Pass ToASCII("U+deddU+def8‍。䜖") C2; V7 +Pass ToASCII("xn--g138cxw05a.xn--k0o") V7 +Pass ToASCII("xn--1ug30527h9mxi.xn--k0o") C2; V7 +Pass ToASCII("ᡯ⚉姶U+dd09.۷‍U+dfaa‍") C2; U1 (ignored) +Pass ToASCII("ᡯ⚉姶8,.۷‍U+dfaa‍") C2; U1 (ignored) +Pass ToASCII("xn--8,-g9oy26fzu4d.xn--kmb6733w") U1 (ignored) +Pass ToASCII("xn--8,-g9oy26fzu4d.xn--kmb859ja94998b") C2; U1 (ignored) +Pass ToASCII("xn--c9e433epi4b3j20a.xn--kmb6733w") V7 +Pass ToASCII("xn--c9e433epi4b3j20a.xn--kmb859ja94998b") C2; V7 +Pass ToASCII("፟ᡈ‌.︒-U+df90-") C1; V6; V7; V3 (ignored) +Pass ToASCII("፟ᡈ‌.。-U+df90-") C1; V6; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--b7d82w..xn-----pe4u") V6; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--b7d82wo4h..xn-----pe4u") C1; V6; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--b7d82w.xn-----c82nz547a") V6; V7; V3 (ignored) +Pass ToASCII("xn--b7d82wo4h.xn-----c82nz547a") C1; V6; V7; V3 (ignored) +Pass ToASCII("U+de5c。-୍Ⴋ") V6; V3 (ignored) +Pass ToASCII("U+de5c。-୍ⴋ") V6; V3 (ignored) +Pass ToASCII("xn--792h.xn----bse820x") V6; V3 (ignored) +Pass ToASCII("xn--792h.xn----bse632b") V6; V7; V3 (ignored) +Pass ToASCII("U+dff5隁⯮.᠍‌") C1 +Pass ToASCII("9隁⯮.᠍‌") C1 +Pass ToASCII("xn--9-mfs8024b.") A4_2 (ignored) +Pass ToASCII("9隁⯮.") A4_2 (ignored) +Pass ToASCII("xn--9-mfs8024b.xn--0ug") C1 +Pass ToASCII("ᮬႬ‌̥。U+dff8") C1; V6 +Pass ToASCII("xn--mta176jjjm.c") V6 +Pass ToASCII("xn--mta176j97cl2q.c") C1; V6 +Pass ToASCII("ᮬⴌ‌̥。U+dff8") C1; V6 +Pass ToASCII("xn--mta930emri.c") V6; V7 +Pass ToASCII("xn--mta930emribme.c") C1; V6; V7 +Pass ToASCII("U+dd01͟⾶。₇︒눇≮") V6; V7 +Pass ToASCII("U+dd01͟⾶。₇︒눇≮") V6; V7 +Pass ToASCII("U+dd01͟飛。7。눇≮") V6 +Pass ToASCII("U+dd01͟飛。7。눇≮") V6 +Pass ToASCII("xn--9ua0567e.7.xn--gdh6767c") V6 +Pass ToASCII("xn--9ua0567e.xn--7-ngou006d1ttc") V6; V7 +Pass ToASCII("xn--2ib43l.xn--te6h") +Pass ToASCII("ٽृ.U+dd35") +Pass ToASCII("ٽृ.U+dd13") +Pass ToASCII("‌。ᅠ྄ྖ") C1; V6 +Pass ToASCII("‌。ᅠ྄ྖ") C1; V6 +Pass ToASCII(".xn--3ed0b") V6; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--3ed0b") C1; V6 +Pass ToASCII(".xn--3ed0b20h") V7; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--3ed0b20h") C1; V7 +Pass ToASCII(".xn--3ed0by082k") V7; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--3ed0by082k") C1; V7 +Pass ToASCII("≯U+de05.‍U+dd7cU+dddb") C2; V7 +Pass ToASCII("≯U+de05.‍U+dd7cU+dddb") C2; V7 +Pass ToASCII("≯U+de05.‍U+dd7cU+dddb") C2; V7 +Pass ToASCII("≯U+de05.‍U+dd7cU+dddb") C2; V7 +Pass ToASCII("xn--hdh84488f.xn--xy7cw2886b") V7 +Pass ToASCII("xn--hdh84488f.xn--1ug8099fbjp4e") C2; V7 +Pass ToASCII("꧐Ӏ᮪ࣶ.눵") +Pass ToASCII("꧐Ӏ᮪ࣶ.눵") +Pass ToASCII("꧐Ӏ᮪ࣶ.눵") +Pass ToASCII("꧐Ӏ᮪ࣶ.눵") +Pass ToASCII("꧐ӏ᮪ࣶ.눵") +Pass ToASCII("꧐ӏ᮪ࣶ.눵") +Pass ToASCII("xn--s5a04sn4u297k.xn--2e1b") +Pass ToASCII("꧐ӏ᮪ࣶ.눵") +Pass ToASCII("꧐ӏ᮪ࣶ.눵") +Pass ToASCII("xn--d5a07sn4u297k.xn--2e1b") V7 +Pass ToASCII("꣪。U+dd3fU+ddbeU+ddd7") V6; V7 +Pass ToASCII("꣪。U+dd3fU+ddbeU+ddd7") V6; V7 +Pass ToASCII("xn--3g9a.xn--ud1dz07k") V6; V7 +Pass ToASCII("U+dcd3U+deb3。U+ddff≯⾇") V7 +Pass ToASCII("U+dcd3U+deb3。U+ddff≯⾇") V7 +Pass ToASCII("U+dcd3U+deb3。U+ddff≯舛") V7 +Pass ToASCII("U+dcd3U+deb3。U+ddff≯舛") V7 +Pass ToASCII("xn--3e2d79770c.xn--hdh0088abyy1c") V7 +Pass ToASCII("xn--9hb7344k.") A4_2 (ignored) +Pass ToASCII("U+dec7١.") A4_2 (ignored) +Pass ToASCII("U+dd48砪≯ᢑ。≯U+de5aU+dd14‌") C1; V7 +Pass ToASCII("U+dd48砪≯ᢑ。≯U+de5aU+dd14‌") C1; V7 +Pass ToASCII("U+dd48砪≯ᢑ。≯U+de5aU+dd14‌") C1; V7 +Pass ToASCII("U+dd48砪≯ᢑ。≯U+de5aU+dd14‌") C1; V7 +Pass ToASCII("xn--bbf561cf95e57y3e.xn--hdh0834o7mj6b") V7 +Pass ToASCII("xn--bbf561cf95e57y3e.xn--0ugz6gc910ejro8c") C1; V7 +Pass ToASCII("Ⴥ.U+dd33㊸") V6 +Pass ToASCII("Ⴥ.U+dd3343") V6 +Pass ToASCII("ⴥ.U+dd3343") V6 +Pass ToASCII("xn--tlj.xn--43-274o") V6 +Pass ToASCII("ⴥ.U+dd33㊸") V6 +Pass ToASCII("xn--9nd.xn--43-274o") V6; V7 +Pass ToASCII("U+dea8U+dd09ᅠྷ.U+dfb0꥓") V7 +Pass ToASCII("U+dea8U+dd09ᅠྷ.U+dfb0꥓") V7 +Pass ToASCII("xn--kgd72212e.xn--3j9au7544a") V7 +Pass ToASCII("xn--kgd36f9z57y.xn--3j9au7544a") V7 +Pass ToASCII("xn--kgd7493jee34a.xn--3j9au7544a") V7 +Pass ToASCII("ؘ.۳‌꥓") C1; V6 +Pass ToASCII("xn--6fb.xn--gmb0524f") V6 +Pass ToASCII("xn--6fb.xn--gmb469jjf1h") C1; V6 +Pass ToASCII("ᡌ.︒ᢑ") V7 +Pass ToASCII("ᡌ.。ᢑ") A4_2 (ignored) +Pass ToASCII("xn--c8e..xn--bbf") A4_2 (ignored) +Pass ToASCII("xn--c8e.xn--bbf9168i") V7 +Pass ToASCII("U+ddcf。ᠢU+de06") V7 +Pass ToASCII("xn--hd7h.xn--46e66060j") V7 +Pass ToASCII("U+ded4U+dd8eU+dd97U+dc95。≮") V7 +Pass ToASCII("U+ded4U+dd8eU+dd97U+dc95。≮") V7 +Pass ToASCII("xn--4m3dv4354a.xn--gdh") V7 +Pass ToASCII("U+dda6.ࣣ暀≠") V6; A4_2 (ignored) +Pass ToASCII("U+dda6.ࣣ暀≠") V6; A4_2 (ignored) +Pass ToASCII(".xn--m0b461k3g2c") V6; A4_2 (ignored) +Pass ToASCII("䂹U+dd85U+dee6.‍") C2; V7 +Pass ToASCII("䂹U+dd85U+dee6.‍") C2; V7 +Pass ToASCII("xn--0on3543c5981i.") V7; A4_2 (ignored) +Pass ToASCII("xn--0on3543c5981i.xn--1ug") C2; V7 +Pass ToASCII("︒。Ⴃ≯") V7 +Pass ToASCII("︒。Ⴃ≯") V7 +Pass ToASCII("。。Ⴃ≯") A4_2 (ignored) +Pass ToASCII("。。Ⴃ≯") A4_2 (ignored) +Pass ToASCII("。。ⴃ≯") A4_2 (ignored) +Pass ToASCII("。。ⴃ≯") A4_2 (ignored) +Pass ToASCII("..xn--hdh782b") A4_2 (ignored) +Pass ToASCII("︒。ⴃ≯") V7 +Pass ToASCII("︒。ⴃ≯") V7 +Pass ToASCII("xn--y86c.xn--hdh782b") V7 +Pass ToASCII("..xn--bnd622g") V7; A4_2 (ignored) +Pass ToASCII("xn--y86c.xn--bnd622g") V7 +Pass ToASCII("箃Ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("箃Ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("箃Ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("箃Ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("箃ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("箃ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("xn----4wsr321ay823p.xn----tfot873s") V7 +Pass ToASCII("箃ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("箃ⴡ-U+dc5d。≠-U+dd16") V7 +Pass ToASCII("xn----11g3013fy8x5m.xn----tfot873s") V7 +Pass ToASCII("ߥ.ڵ") +Pass ToASCII("xn--dtb.xn--okb") +Pass ToASCII(".xn--3e6h") A4_2 (ignored) +Pass ToASCII("xn--3e6h") +Pass ToASCII("U+dd3f") +Pass ToASCII("U+dd1d") +Pass ToASCII("်‍‌。-‌") C1; V6; V3 (ignored) +Pass ToASCII("xn--bkd.-") V6; V3 (ignored) +Pass ToASCII("xn--bkd412fca.xn----sgn") C1; V6; V3 (ignored) +Pass ToASCII("︒。᭄ᡉ") V6; V7 +Pass ToASCII("。。᭄ᡉ") V6; A4_2 (ignored) +Pass ToASCII("..xn--87e93m") V6; A4_2 (ignored) +Pass ToASCII("xn--y86c.xn--87e93m") V6; V7 +Pass ToASCII("-᮫︒‍.U+dd88U+de53") C2; V7; V3 (ignored) +Pass ToASCII("-᮫。‍.U+dd88U+de53") C2; V7; V3 (ignored) +Pass ToASCII("xn----qml..xn--x50zy803a") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----qml.xn--1ug.xn--x50zy803a") C2; V7; V3 (ignored) +Pass ToASCII("xn----qml1407i.xn--x50zy803a") V7; V3 (ignored) +Pass ToASCII("xn----qmlv7tw180a.xn--x50zy803a") C2; V7; V3 (ignored) +Pass ToASCII("U+ddae.≯U+dc06") V7 +Pass ToASCII("U+ddae.≯U+dc06") V7 +Pass ToASCII("xn--t546e.xn--hdh5166o") V7 +Pass ToASCII("ڹ.ᡳᅟ") +Pass ToASCII("ڹ.ᡳᅟ") +Pass ToASCII("xn--skb.xn--g9e") +Pass ToASCII("ڹ.ᡳ") +Pass ToASCII("xn--skb.xn--osd737a") V7 +Pass ToASCII("㨛U+dc4e.︒U+dfd5ഁ") V7 +Pass ToASCII("㨛U+dc4e.。7ഁ") A4_2 (ignored) +Pass ToASCII("xn--mbm8237g..xn--7-7hf") A4_2 (ignored) +Pass ToASCII("xn--mbm8237g.xn--7-7hf1526p") V7 +Pass ToASCII("ß‌꫶ᢥ.⊶ჁႶ") C1 +Pass ToASCII("ß‌꫶ᢥ.⊶ჁႶ") C1 +Pass ToASCII("ß‌꫶ᢥ.⊶ⴡⴖ") C1 +Pass ToASCII("SS‌꫶ᢥ.⊶ჁႶ") C1 +Pass ToASCII("ss‌꫶ᢥ.⊶ⴡⴖ") C1 +Pass ToASCII("Ss‌꫶ᢥ.⊶Ⴡⴖ") C1 +Pass ToASCII("xn--ss-4epx629f.xn--ifh802b6a") +Pass ToASCII("ss꫶ᢥ.⊶ⴡⴖ") +Pass ToASCII("SS꫶ᢥ.⊶ჁႶ") +Pass ToASCII("Ss꫶ᢥ.⊶Ⴡⴖ") +Pass ToASCII("xn--ss-4ep585bkm5p.xn--ifh802b6a") C1 +Pass ToASCII("xn--zca682johfi89m.xn--ifh802b6a") C1 +Pass ToASCII("ß‌꫶ᢥ.⊶ⴡⴖ") C1 +Pass ToASCII("SS‌꫶ᢥ.⊶ჁႶ") C1 +Pass ToASCII("ss‌꫶ᢥ.⊶ⴡⴖ") C1 +Pass ToASCII("Ss‌꫶ᢥ.⊶Ⴡⴖ") C1 +Pass ToASCII("xn--ss-4epx629f.xn--5nd703gyrh") V7 +Pass ToASCII("xn--ss-4ep585bkm5p.xn--5nd703gyrh") C1; V7 +Pass ToASCII("xn--ss-4epx629f.xn--undv409k") V7 +Pass ToASCII("xn--ss-4ep585bkm5p.xn--undv409k") C1; V7 +Pass ToASCII("xn--zca682johfi89m.xn--undv409k") C1; V7 +Pass ToASCII("‍。ςU+dc49") C2; V7 +Pass ToASCII("‍。ΣU+dc49") C2; V7 +Pass ToASCII("‍。σU+dc49") C2; V7 +Pass ToASCII(".xn--4xa24344p") V7; A4_2 (ignored) +Pass ToASCII("xn--1ug.xn--4xa24344p") C2; V7 +Pass ToASCII("xn--1ug.xn--3xa44344p") C2; V7 +Pass ToASCII("⒒U+de19U+dce0U+dcc0.-U+dc4a") V7; V3 (ignored) +Pass ToASCII("11.U+de19U+dce0U+dcc0.-U+dc4a") V7; V3 (ignored) +Pass ToASCII("11.xn--uz1d59632bxujd.xn----x310m") V7; V3 (ignored) +Pass ToASCII("xn--3shy698frsu9dt1me.xn----x310m") V7; V3 (ignored) +Pass ToASCII("-。‍") C2; V3 (ignored) +Pass ToASCII("-。‍") C2; V3 (ignored) +Pass ToASCII("-.") V3 (ignored); A4_2 (ignored) +Pass ToASCII("-.xn--1ug") C2; V3 (ignored) +Pass ToASCII("ቬU+dc3cU+ddf6。U+de2cU+dfe0") V7 +Pass ToASCII("ቬU+dc3cU+ddf6。U+de2c8") V7 +Pass ToASCII("xn--d0d41273c887z.xn--8-ob5i") V7 +Pass ToASCII("ς‍-.ჃU+dfd9") C2; V3 (ignored) +Pass ToASCII("ς‍-.ⴣU+dfd9") C2; V3 (ignored) +Pass ToASCII("Σ‍-.ჃU+dfd9") C2; V3 (ignored) +Pass ToASCII("σ‍-.ⴣU+dfd9") C2; V3 (ignored) +Pass ToASCII("xn----zmb.xn--rlj2573p") V3 (ignored) +Pass ToASCII("xn----zmb048s.xn--rlj2573p") C2; V3 (ignored) +Pass ToASCII("xn----xmb348s.xn--rlj2573p") C2; V3 (ignored) +Pass ToASCII("xn----zmb.xn--7nd64871a") V7; V3 (ignored) +Pass ToASCII("xn----zmb048s.xn--7nd64871a") C2; V7; V3 (ignored) +Pass ToASCII("xn----xmb348s.xn--7nd64871a") C2; V7; V3 (ignored) +Pass ToASCII("≠。U+dfb3U+dff2") +Pass ToASCII("≠。U+dfb3U+dff2") +Pass ToASCII("≠。U+dfb36") +Pass ToASCII("≠。U+dfb36") +Pass ToASCII("xn--1ch.xn--6-dl4s") +Pass ToASCII("≠.U+dfb36") +Pass ToASCII("≠.U+dfb36") +Pass ToASCII("U+df3d.蠔") V7 +Pass ToASCII("xn--g747d.xn--xl2a") V7 +Pass ToASCII("ࣦ‍.뼽") C2; V6 +Pass ToASCII("ࣦ‍.뼽") C2; V6 +Pass ToASCII("ࣦ‍.뼽") C2; V6 +Pass ToASCII("ࣦ‍.뼽") C2; V6 +Pass ToASCII("xn--p0b.xn--e43b") V6 +Pass ToASCII("xn--p0b869i.xn--e43b") C2; V6 +Pass ToASCII("U+de3d.U+de15") V7 +Pass ToASCII("U+de3d.U+de15") V7 +Pass ToASCII("xn--pr3x.xn--rv7w") V7 +Pass ToASCII("U+dfc0U+de09U+ddcf。U+dea7₄ႫU+de6b") V7 +Pass ToASCII("U+dfc0U+de09U+ddcf。U+dea74ႫU+de6b") V7 +Pass ToASCII("U+dfc0U+de09U+ddcf。U+dea74ⴋU+de6b") V7 +Pass ToASCII("xn--039c42bq865a.xn--4-wvs27840bnrzm") V7 +Pass ToASCII("U+dfc0U+de09U+ddcf。U+dea7₄ⴋU+de6b") V7 +Pass ToASCII("xn--039c42bq865a.xn--4-t0g49302fnrzm") V7 +Pass ToASCII("U+dfd3。ۗ") V6 +Pass ToASCII("5。ۗ") V6 +Pass ToASCII("5.xn--nlb") V6 +Pass ToASCII("‌U+de29.⾕") C1; V7 +Pass ToASCII("‌U+de29.谷") C1; V7 +Pass ToASCII("xn--i183d.xn--6g3a") V7 +Pass ToASCII("xn--0ug26167i.xn--6g3a") C1; V7 +Pass ToASCII("︒U+dc07‍.-ܼ‌") C1; C2; V7; V3 (ignored) +Pass ToASCII("。U+dc07‍.-ܼ‌") C1; C2; V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII(".xn--hh50e.xn----t2c") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII(".xn--1ug05310k.xn----t2c071q") C1; C2; V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--y86c71305c.xn----t2c") V7; V3 (ignored) +Pass ToASCII("xn--1ug1658ftw26f.xn----t2c071q") C1; C2; V7; V3 (ignored) +Pass ToASCII("‍.U+dfd7") C2 +Pass ToASCII("‍.j") C2 +Pass ToASCII("‍.J") C2 +Pass ToASCII(".j") A4_2 (ignored) +Pass ToASCII("xn--1ug.j") C2 +Pass ToASCII("j") +Pass ToASCII("ႭU+dccdꡨ֮。Ⴞ‌‌") C1; V7 +Pass ToASCII("ⴍU+dccdꡨ֮。ⴞ‌‌") C1; V7 +Pass ToASCII("xn--5cb172r175fug38a.xn--mlj") V7 +Pass ToASCII("xn--5cb172r175fug38a.xn--0uga051h") C1; V7 +Pass ToASCII("xn--5cb347co96jug15a.xn--2nd") V7 +Pass ToASCII("xn--5cb347co96jug15a.xn--2nd059ea") C1; V7 +Pass ToASCII("U+def0。U+dcf1") V7 +Pass ToASCII("xn--k97c.xn--q031e") V7 +Pass ToASCII("ࣟႫU+dff8귤.U+dd7cU+dfe2휪ૣ") V6; V7 +Pass ToASCII("ࣟႫU+dff8귤.U+dd7cU+dfe2휪ૣ") V6; V7 +Pass ToASCII("ࣟႫU+dff8귤.U+dd7c0휪ૣ") V6; V7 +Pass ToASCII("ࣟႫU+dff8귤.U+dd7c0휪ૣ") V6; V7 +Pass ToASCII("ࣟⴋU+dff8귤.U+dd7c0휪ૣ") V6; V7 +Pass ToASCII("ࣟⴋU+dff8귤.U+dd7c0휪ૣ") V6; V7 +Pass ToASCII("xn--i0b436pkl2g2h42a.xn--0-8le8997mulr5f") V6; V7 +Pass ToASCII("ࣟⴋU+dff8귤.U+dd7cU+dfe2휪ૣ") V6; V7 +Pass ToASCII("ࣟⴋU+dff8귤.U+dd7cU+dfe2휪ૣ") V6; V7 +Pass ToASCII("xn--i0b601b6r7l2hs0a.xn--0-8le8997mulr5f") V6; V7 +Pass ToASCII("ބ.U+dc5d؁") V7 +Pass ToASCII("ބ.U+dc5d؁") V7 +Pass ToASCII("xn--lqb.xn--jfb1808v") V7 +Pass ToASCII("્₃.8꣄‍U+dce4") V6 +Pass ToASCII("્3.8꣄‍U+dce4") V6 +Pass ToASCII("xn--3-yke.xn--8-sl4et308f") V6 +Pass ToASCII("xn--3-yke.xn--8-ugnv982dbkwm") V6 +Pass ToASCII("ꡕ≠ၞU+dff1。U+dd67U+dd2bᅠ") V7 +Pass ToASCII("ꡕ≠ၞU+dff1。U+dd67U+dd2bᅠ") V7 +Pass ToASCII("ꡕ≠ၞU+dff1。U+dd67U+dd2bᅠ") V7 +Pass ToASCII("ꡕ≠ၞU+dff1。U+dd67U+dd2bᅠ") V7 +Pass ToASCII("xn--cld333gn31h0158l.xn--3g0d") V7 +Pass ToASCII("鱊。‌") C1 +Pass ToASCII("xn--rt6a.") A4_2 (ignored) +Pass ToASCII("鱊.") A4_2 (ignored) +Pass ToASCII("xn--rt6a.xn--0ug") C1 +Pass ToASCII("xn--4-0bd15808a.") A4_2 (ignored) +Pass ToASCII("U+dd3aߌ4.") A4_2 (ignored) +Pass ToASCII("U+dd18ߌ4.") A4_2 (ignored) +Pass ToASCII("-。䏛") V3 (ignored) +Pass ToASCII("-。䏛") V3 (ignored) +Pass ToASCII("-.xn--xco") V3 (ignored) +Pass ToASCII("‌U+dce0.‍") C1; C2; V7 +Pass ToASCII("‌U+dce0.‍") C1; C2; V7 +Pass ToASCII("xn--dj8y.") V7; A4_2 (ignored) +Pass ToASCII("xn--0ugz7551c.xn--1ug") C1; C2; V7 +Pass ToASCII("U+ddc0.U+de31") V6; V7 +Pass ToASCII("xn--wd1d.xn--k946e") V6; V7 +Pass ToASCII("⾆.ꡈ5≯ß") +Pass ToASCII("⾆.ꡈ5≯ß") +Pass ToASCII("舌.ꡈ5≯ß") +Pass ToASCII("舌.ꡈ5≯ß") +Pass ToASCII("舌.ꡈ5≯SS") +Pass ToASCII("舌.ꡈ5≯SS") +Pass ToASCII("舌.ꡈ5≯ss") +Pass ToASCII("舌.ꡈ5≯ss") +Pass ToASCII("舌.ꡈ5≯Ss") +Pass ToASCII("舌.ꡈ5≯Ss") +Pass ToASCII("xn--tc1a.xn--5ss-3m2a5009e") +Pass ToASCII("xn--tc1a.xn--5-qfa988w745i") +Pass ToASCII("⾆.ꡈ5≯SS") +Pass ToASCII("⾆.ꡈ5≯SS") +Pass ToASCII("⾆.ꡈ5≯ss") +Pass ToASCII("⾆.ꡈ5≯ss") +Pass ToASCII("⾆.ꡈ5≯Ss") +Pass ToASCII("⾆.ꡈ5≯Ss") +Pass ToASCII("U+dd2a.ς") +Pass ToASCII("U+dd08.Σ") +Pass ToASCII("U+dd2a.σ") +Pass ToASCII("U+dd08.σ") +Pass ToASCII("xn--ie6h.xn--4xa") +Pass ToASCII("U+dd08.ς") +Pass ToASCII("xn--ie6h.xn--3xa") +Pass ToASCII("U+dd2a.Σ") +Pass ToASCII("‌Ⴚ。ς") C1 +Pass ToASCII("‌Ⴚ。ς") C1 +Pass ToASCII("‌ⴚ。ς") C1 +Pass ToASCII("‌Ⴚ。Σ") C1 +Pass ToASCII("‌ⴚ。σ") C1 +Pass ToASCII("xn--ilj.xn--4xa") +Pass ToASCII("ⴚ.σ") +Pass ToASCII("Ⴚ.Σ") +Pass ToASCII("ⴚ.ς") +Pass ToASCII("Ⴚ.ς") +Pass ToASCII("xn--ilj.xn--3xa") +Pass ToASCII("Ⴚ.σ") +Pass ToASCII("xn--0ug262c.xn--4xa") C1 +Pass ToASCII("xn--0ug262c.xn--3xa") C1 +Pass ToASCII("‌ⴚ。ς") C1 +Pass ToASCII("‌Ⴚ。Σ") C1 +Pass ToASCII("‌ⴚ。σ") C1 +Pass ToASCII("xn--ynd.xn--4xa") V7 +Pass ToASCII("xn--ynd.xn--3xa") V7 +Pass ToASCII("xn--ynd759e.xn--4xa") C1; V7 +Pass ToASCII("xn--ynd759e.xn--3xa") C1; V7 +Pass ToASCII("‍⾕。‌꥓̐ꡎ") C1; C2 +Pass ToASCII("‍⾕。‌꥓̐ꡎ") C1; C2 +Pass ToASCII("‍谷。‌꥓̐ꡎ") C1; C2 +Pass ToASCII("xn--6g3a.xn--0sa8175flwa") V6 +Pass ToASCII("xn--1ug0273b.xn--0sa359l6n7g13a") C1; C2 +Pass ToASCII("淽。ᠾ") +Pass ToASCII("xn--34w.xn--x7e") +Pass ToASCII("淽.ᠾ") +Pass ToASCII("U+de29Ⴓ❓。U+dd28") V6; V7 +Pass ToASCII("U+de29Ⴓ❓。U+dd28") V6; V7 +Pass ToASCII("U+de29ⴓ❓。U+dd28") V6; V7 +Pass ToASCII("xn--8di78qvw32y.xn--k80d") V6; V7 +Pass ToASCII("U+de29ⴓ❓。U+dd28") V6; V7 +Pass ToASCII("xn--rnd896i0j14q.xn--k80d") V6; V7 +Pass ToASCII("៿。U+df33") V7 +Pass ToASCII("៿。U+df33") V7 +Pass ToASCII("xn--45e.xn--et6h") V7 +Pass ToASCII("ْ‍。್U+deb3") C2; V6 +Pass ToASCII("ْ‍。್U+deb3") C2; V6 +Pass ToASCII("xn--uhb.xn--8tc4527k") V6 +Pass ToASCII("xn--uhb882k.xn--8tc4527k") C2; V6 +Pass ToASCII("ßU+dc3bU+df17。U+de68U+dd6eß") V6; V7 +Pass ToASCII("ßU+dc3bU+df17。U+de68U+dd6eß") V6; V7 +Pass ToASCII("SSU+dc3bU+df17。U+de68U+dd6eSS") V6; V7 +Pass ToASCII("ssU+dc3bU+df17。U+de68U+dd6ess") V6; V7 +Pass ToASCII("SsU+dc3bU+df17。U+de68U+dd6eSs") V6; V7 +Pass ToASCII("xn--ss-jl59biy67d.xn--ss-4d11aw87d") V6; V7 +Pass ToASCII("xn--zca20040bgrkh.xn--zca3653v86qa") V6; V7 +Pass ToASCII("SSU+dc3bU+df17。U+de68U+dd6eSS") V6; V7 +Pass ToASCII("ssU+dc3bU+df17。U+de68U+dd6ess") V6; V7 +Pass ToASCII("SsU+dc3bU+df17。U+de68U+dd6eSs") V6; V7 +Pass ToASCII("‍。‌") C1; C2 +Pass ToASCII("xn--1ug.xn--0ug") C1; C2 +Pass ToASCII("U+dc58.U+dd2e") V7; A4_2 (ignored) +Pass ToASCII("U+dc58.U+dd2e") V7; A4_2 (ignored) +Pass ToASCII("xn--s136e.") V7; A4_2 (ignored) +Pass ToASCII("ꦷU+dd59멹。⒛U+de07") V6; V7 +Pass ToASCII("ꦷU+dd59멹。⒛U+de07") V6; V7 +Pass ToASCII("ꦷU+dd59멹。20.U+de07") V6; V7 +Pass ToASCII("ꦷU+dd59멹。20.U+de07") V6; V7 +Pass ToASCII("xn--ym9av13acp85w.20.xn--d846e") V6; V7 +Pass ToASCII("xn--ym9av13acp85w.xn--dth22121k") V6; V7 +Pass ToASCII("‌。︒") C1; V7 +Pass ToASCII("‌。。") C1; A4_2 (ignored) +Pass ToASCII("..") A4_2 (ignored) +Pass ToASCII("xn--0ug..") C1; A4_2 (ignored) +Pass ToASCII(".xn--y86c") V7; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--y86c") C1; V7 +Pass ToASCII("ᡲ-U+dff9.ß-‌-") C1; V3 (ignored) +Pass ToASCII("ᡲ-3.ß-‌-") C1; V3 (ignored) +Pass ToASCII("ᡲ-3.SS-‌-") C1; V3 (ignored) +Pass ToASCII("ᡲ-3.ss-‌-") C1; V3 (ignored) +Pass ToASCII("ᡲ-3.Ss-‌-") C1; V3 (ignored) +Pass ToASCII("xn---3-p9o.ss--") V2 (ignored); V3 (ignored) +Pass ToASCII("xn---3-p9o.xn--ss---276a") C1; V3 (ignored) +Pass ToASCII("xn---3-p9o.xn-----fia9303a") C1; V3 (ignored) +Pass ToASCII("ᡲ-U+dff9.SS-‌-") C1; V3 (ignored) +Pass ToASCII("ᡲ-U+dff9.ss-‌-") C1; V3 (ignored) +Pass ToASCII("ᡲ-U+dff9.Ss-‌-") C1; V3 (ignored) +Pass ToASCII("U+dd9cᢘ。᩿⺢") V6; V7 +Pass ToASCII("xn--ibf35138o.xn--fpfz94g") V6; V7 +Pass ToASCII("U+dda7U+dfef。⒈᩶U+dfdaU+de0c") V7 +Pass ToASCII("U+dda73。1.᩶2U+de0c") V6; V7 +Pass ToASCII("xn--3-rj42h.1.xn--2-13k96240l") V6; V7 +Pass ToASCII("xn--3-rj42h.xn--2-13k746cq465x") V7 +Pass ToASCII("‍₅⒈。≯U+dff4‍") C2; V7 +Pass ToASCII("‍₅⒈。≯U+dff4‍") C2; V7 +Pass ToASCII("‍51.。≯8‍") C2; A4_2 (ignored) +Pass ToASCII("‍51.。≯8‍") C2; A4_2 (ignored) +Pass ToASCII("51..xn--8-ogo") A4_2 (ignored) +Pass ToASCII("xn--51-l1t..xn--8-ugn00i") C2; A4_2 (ignored) +Pass ToASCII("xn--5-ecp.xn--8-ogo") V7 +Pass ToASCII("xn--5-tgnz5r.xn--8-ugn00i") C2; V7 +Pass ToASCII("U+ddc2ੂႪU+dc9f.≮") V7 +Pass ToASCII("U+ddc2ੂႪU+dc9f.≮") V7 +Pass ToASCII("U+ddc2ੂⴊU+dc9f.≮") V7 +Pass ToASCII("U+ddc2ੂⴊU+dc9f.≮") V7 +Pass ToASCII("xn--nbc229o4y27dgskb.xn--gdh") V7 +Pass ToASCII("xn--nbc493aro75ggskb.xn--gdh") V7 +Pass ToASCII("ꡠ.۲") +Pass ToASCII("ꡠ.۲") +Pass ToASCII("xn--5c9a.xn--fmb") +Pass ToASCII("꙽‌U+ddf5U+dd06。‌U+dc42ᬁ") C1; V6; U1 (ignored) +Pass ToASCII("꙽‌霣U+dd06。‌U+dc42ᬁ") C1; V6; U1 (ignored) +Pass ToASCII("꙽‌霣5,。‌U+dc42ᬁ") C1; V6; U1 (ignored) +Pass ToASCII("xn--5,-op8g373c.xn--4sf0725i") V6; U1 (ignored) +Pass ToASCII("xn--5,-i1tz135dnbqa.xn--4sf36u6u4w") C1; V6; U1 (ignored) +Pass ToASCII("xn--2q5a751a653w.xn--4sf0725i") V6; V7 +Pass ToASCII("xn--0ug4208b2vjuk63a.xn--4sf36u6u4w") C1; V6; V7 +Pass ToASCII("兎。ᠼU+dd1cU+deb6U+dc3f") V7 +Pass ToASCII("兎。ᠼU+dd1cU+deb6U+dc3f") V7 +Pass ToASCII("xn--b5q.xn--v7e6041kqqd4m251b") V7 +Pass ToASCII("U+dfd9。‍U+dff8‍⁷") C2 +Pass ToASCII("1。‍2‍7") C2 +Pass ToASCII("1.2h") +Pass ToASCII("1.xn--27-l1tb") C2 +Pass ToASCII("ᡨ-。U+decbU+dff7") V7; V3 (ignored) +Pass ToASCII("ᡨ-。U+decb1") V7; V3 (ignored) +Pass ToASCII("xn----z8j.xn--1-5671m") V7; V3 (ignored) +Pass ToASCII("ႼU+ddedྀ⾇。Ⴏ♀‌‌") C1; V7 +Pass ToASCII("ႼU+ddedྀ舛。Ⴏ♀‌‌") C1; V7 +Pass ToASCII("ⴜU+ddedྀ舛。ⴏ♀‌‌") C1; V7 +Pass ToASCII("xn--zed372mdj2do3v4h.xn--e5h11w") V7 +Pass ToASCII("xn--zed372mdj2do3v4h.xn--0uga678bgyh") C1; V7 +Pass ToASCII("ⴜU+ddedྀ⾇。ⴏ♀‌‌") C1; V7 +Pass ToASCII("xn--zed54dz10wo343g.xn--nnd651i") V7 +Pass ToASCII("xn--zed54dz10wo343g.xn--nnd089ea464d") C1; V7 +Pass ToASCII("U+dc46U+dff0.‍") C2; V6 +Pass ToASCII("U+dc464.‍") C2; V6 +Pass ToASCII("xn--4-xu7i.") V6; A4_2 (ignored) +Pass ToASCII("xn--4-xu7i.xn--1ug") C2; V6 +Pass ToASCII("U+dd18Ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("U+dd18Ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("U+dd18Ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("U+dd18Ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("U+dd18ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("U+dd18ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("xn--mlju35u7qx2f.xn--et3bn23n") V6; V7 +Pass ToASCII("xn--mlju35u7qx2f.xn--0ugb6122js83c") C1; V6; V7 +Pass ToASCII("U+dd18ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("U+dd18ⴞ癀。U+de3f‍‌붼") C1; V6; V7 +Pass ToASCII("xn--2nd6803c7q37d.xn--et3bn23n") V6; V7 +Pass ToASCII("xn--2nd6803c7q37d.xn--0ugb6122js83c") C1; V6; V7 +Pass ToASCII("ᡃU+dfe7≯ᠣ.氁U+dff1ꁫ") V7 +Pass ToASCII("ᡃU+dfe7≯ᠣ.氁U+dff1ꁫ") V7 +Pass ToASCII("ᡃ5≯ᠣ.氁U+dff1ꁫ") V7 +Pass ToASCII("ᡃ5≯ᠣ.氁U+dff1ꁫ") V7 +Pass ToASCII("xn--5-24jyf768b.xn--lqw213ime95g") V7 +Pass ToASCII("-U+de36⒏.⒎U+dee2U+dfad") V7; V3 (ignored) +Pass ToASCII("-U+de368..7.U+dee2U+dfad") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn---8-bv5o..7.xn--c35nf1622b") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----scp6252h.xn--zshy411yzpx2d") V7; V3 (ignored) +Pass ToASCII("‌Ⴁ畝‍.≮") C1; C2 +Pass ToASCII("‌Ⴁ畝‍.≮") C1; C2 +Pass ToASCII("‌Ⴁ畝‍.≮") C1; C2 +Pass ToASCII("‌Ⴁ畝‍.≮") C1; C2 +Pass ToASCII("‌ⴁ畝‍.≮") C1; C2 +Pass ToASCII("‌ⴁ畝‍.≮") C1; C2 +Pass ToASCII("xn--skjy82u.xn--gdh") +Pass ToASCII("ⴁ畝.≮") +Pass ToASCII("ⴁ畝.≮") +Pass ToASCII("Ⴁ畝.≮") +Pass ToASCII("Ⴁ畝.≮") +Pass ToASCII("xn--0ugc160hb36e.xn--gdh") C1; C2 +Pass ToASCII("‌ⴁ畝‍.≮") C1; C2 +Pass ToASCII("‌ⴁ畝‍.≮") C1; C2 +Pass ToASCII("xn--8md0962c.xn--gdh") V7 +Pass ToASCII("xn--8md700fea3748f.xn--gdh") C1; C2; V7 +Pass ToASCII("໋‍.鎁U+dc11") C2; V6; V7 +Pass ToASCII("໋‍.鎁U+dc11") C2; V6; V7 +Pass ToASCII("xn--t8c.xn--iz4a43209d") V6; V7 +Pass ToASCII("xn--t8c059f.xn--iz4a43209d") C2; V6; V7 +Pass ToASCII("U+def4.-ᡢ֒U+de20") V7; V3 (ignored) +Pass ToASCII("xn--ep37b.xn----hec165lho83b") V7; V3 (ignored) +Pass ToASCII("U+dc2b.᮪ςႦ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪ςႦ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪ςⴆ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪ΣႦ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪σⴆ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪Σⴆ‍") C2; V6; V7 +Pass ToASCII("xn--nu4s.xn--4xa153j7im") V6; V7 +Pass ToASCII("xn--nu4s.xn--4xa153jk8cs1q") C2; V6; V7 +Pass ToASCII("xn--nu4s.xn--3xa353jk8cs1q") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪ςⴆ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪ΣႦ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪σⴆ‍") C2; V6; V7 +Pass ToASCII("U+dc2b.᮪Σⴆ‍") C2; V6; V7 +Pass ToASCII("xn--nu4s.xn--4xa217dxri") V6; V7 +Pass ToASCII("xn--nu4s.xn--4xa217dxriome") C2; V6; V7 +Pass ToASCII("xn--nu4s.xn--3xa417dxriome") C2; V6; V7 +Pass ToASCII("⒈‌ꫬ︒.્") C1; V6; V7 +Pass ToASCII("1.‌ꫬ。.્") C1; V6; A4_2 (ignored) +Pass ToASCII("1.xn--sv9a..xn--mfc") V6; A4_2 (ignored) +Pass ToASCII("1.xn--0ug7185c..xn--mfc") C1; V6; A4_2 (ignored) +Pass ToASCII("xn--tsh0720cse8b.xn--mfc") V6; V7 +Pass ToASCII("xn--0ug78o720myr1c.xn--mfc") C1; V6; V7 +Pass ToASCII("ß‍.᯲U+dfbc") C2; V6; V7 +Pass ToASCII("SS‍.᯲U+dfbc") C2; V6; V7 +Pass ToASCII("ss‍.᯲U+dfbc") C2; V6; V7 +Pass ToASCII("Ss‍.᯲U+dfbc") C2; V6; V7 +Pass ToASCII("ss.xn--0zf22107b") V6; V7 +Pass ToASCII("xn--ss-n1t.xn--0zf22107b") C2; V6; V7 +Pass ToASCII("xn--zca870n.xn--0zf22107b") C2; V6; V7 +Pass ToASCII("U+dcc2‌≮.≮") V6 +Pass ToASCII("U+dcc2‌≮.≮") V6 +Pass ToASCII("xn--gdhz656g.xn--gdh") V6 +Pass ToASCII("xn--0ugy6glz29a.xn--gdh") V6 +Pass ToASCII("U+dd7c.ᅠ") A4_2 (ignored) +Pass ToASCII("U+dd7c.ᅠ") A4_2 (ignored) +Pass ToASCII("xn--my8h.") A4_2 (ignored) +Pass ToASCII("U+dd7c.") A4_2 (ignored) +Pass ToASCII("xn--my8h.xn--psd") V7 +Pass ToASCII("xn--my8h.xn--cl7c") V7 +Pass ToASCII("爕U+de51.U+dff0気") V7 +Pass ToASCII("爕U+de51.4気") V7 +Pass ToASCII("xn--1zxq3199c.xn--4-678b") V7 +Pass ToASCII("U+df43。U+dd83U+dc97--") V7; V2 (ignored); V3 (ignored) +Pass ToASCII("xn--2y75e.xn-----1l15eer88n") V7; V2 (ignored); V3 (ignored) +Pass ToASCII("蔰。U+dc79ࣝ-U+de35") V7 +Pass ToASCII("xn--sz1a.xn----mrd9984r3dl0i") V7 +Pass ToASCII("ςჅ。ݚ") +Pass ToASCII("ςⴥ。ݚ") +Pass ToASCII("ΣჅ。ݚ") +Pass ToASCII("σⴥ。ݚ") +Pass ToASCII("Σⴥ。ݚ") +Pass ToASCII("xn--4xa203s.xn--epb") +Pass ToASCII("σⴥ.ݚ") +Pass ToASCII("ΣჅ.ݚ") +Pass ToASCII("Σⴥ.ݚ") +Pass ToASCII("xn--3xa403s.xn--epb") +Pass ToASCII("ςⴥ.ݚ") +Pass ToASCII("xn--4xa477d.xn--epb") V7 +Pass ToASCII("xn--3xa677d.xn--epb") V7 +Pass ToASCII("xn--vkb.xn--08e172a") +Pass ToASCII("ڼ.ẏᡤ") +Pass ToASCII("ڼ.ẏᡤ") +Pass ToASCII("ڼ.Ẏᡤ") +Pass ToASCII("ڼ.Ẏᡤ") +Pass ToASCII("xn--pt9c.xn--0kjya") +Pass ToASCII("U+de57.ⴉⴕ") +Pass ToASCII("U+de57.ႩႵ") +Pass ToASCII("U+de57.Ⴉⴕ") +Pass ToASCII("xn--pt9c.xn--hnd666l") V7 +Pass ToASCII("xn--pt9c.xn--hndy") V7 +Pass ToASCII("‌‌ㄤ.̮U+de11ূ") C1; V6; V7 +Pass ToASCII("‌‌ㄤ.̮U+de11ূ") C1; V6; V7 +Pass ToASCII("xn--1fk.xn--vta284a9o563a") V6; V7 +Pass ToASCII("xn--0uga242k.xn--vta284a9o563a") C1; V6; V7 +Pass ToASCII("ႴU+de28₃U+dc66.U+dff3U+dcb9ஂ") V7 +Pass ToASCII("ႴU+de283U+dc66.7U+dcb9ஂ") V7 +Pass ToASCII("ⴔU+de283U+dc66.7U+dcb9ஂ") V7 +Pass ToASCII("xn--3-ews6985n35s3g.xn--7-cve6271r") V7 +Pass ToASCII("ⴔU+de28₃U+dc66.U+dff3U+dcb9ஂ") V7 +Pass ToASCII("xn--3-b1g83426a35t0g.xn--7-cve6271r") V7 +Pass ToASCII("䏈‌。‌⒈U+dc95") C1; V7 +Pass ToASCII("䏈‌。‌1.U+dc95") C1; V7 +Pass ToASCII("xn--eco.1.xn--ms39a") V7 +Pass ToASCII("xn--0ug491l.xn--1-rgn.xn--ms39a") C1; V7 +Pass ToASCII("xn--eco.xn--tsh21126d") V7 +Pass ToASCII("xn--0ug491l.xn--0ug88oot66q") C1; V7 +Pass ToASCII("1꫶ßU+dca5。ᷘ") V6 +Pass ToASCII("1꫶ßU+dca5。ᷘ") V6 +Pass ToASCII("1꫶SSU+dca5。ᷘ") V6 +Pass ToASCII("1꫶ssU+dca5。ᷘ") V6 +Pass ToASCII("xn--1ss-ir6ln166b.xn--weg") V6 +Pass ToASCII("xn--1-qfa2471kdb0d.xn--weg") V6 +Pass ToASCII("1꫶SSU+dca5。ᷘ") V6 +Pass ToASCII("1꫶ssU+dca5。ᷘ") V6 +Pass ToASCII("1꫶SsU+dca5。ᷘ") V6 +Pass ToASCII("1꫶SsU+dca5。ᷘ") V6 +Pass ToASCII("xn--3j78f.xn--mkb20b") V7 +Pass ToASCII("U+dd31⒛⾳.ꡦ⒈") V7 +Pass ToASCII("U+dd3120.音.ꡦ1.") V7; A4_2 (ignored) +Pass ToASCII("xn--20-9802c.xn--0w5a.xn--1-eg4e.") V7; A4_2 (ignored) +Pass ToASCII("xn--dth6033bzbvx.xn--tsh9439b") V7 +Pass ToASCII("Ⴕ。۰≮ß݅") +Pass ToASCII("Ⴕ。۰≮ß݅") +Pass ToASCII("ⴕ。۰≮ß݅") +Pass ToASCII("ⴕ。۰≮ß݅") +Pass ToASCII("Ⴕ。۰≮SS݅") +Pass ToASCII("Ⴕ。۰≮SS݅") +Pass ToASCII("ⴕ。۰≮ss݅") +Pass ToASCII("ⴕ。۰≮ss݅") +Pass ToASCII("Ⴕ。۰≮Ss݅") +Pass ToASCII("Ⴕ。۰≮Ss݅") +Pass ToASCII("xn--dlj.xn--ss-jbe65aw27i") +Pass ToASCII("ⴕ.۰≮ss݅") +Pass ToASCII("ⴕ.۰≮ss݅") +Pass ToASCII("Ⴕ.۰≮SS݅") +Pass ToASCII("Ⴕ.۰≮SS݅") +Pass ToASCII("Ⴕ.۰≮Ss݅") +Pass ToASCII("Ⴕ.۰≮Ss݅") +Pass ToASCII("xn--dlj.xn--zca912alh227g") +Pass ToASCII("ⴕ.۰≮ß݅") +Pass ToASCII("ⴕ.۰≮ß݅") +Pass ToASCII("xn--tnd.xn--ss-jbe65aw27i") V7 +Pass ToASCII("xn--tnd.xn--zca912alh227g") V7 +Pass ToASCII("xn--ge6h.xn--oc9a") +Pass ToASCII("U+dd28.ꡏ") +Pass ToASCII("U+dd06.ꡏ") +Pass ToASCII("ℲU+dd7aU+dd52。≯⾑") V7 +Pass ToASCII("ℲU+dd7aU+dd52。≯⾑") V7 +Pass ToASCII("ℲU+dd7aU+dd52。≯襾") V7 +Pass ToASCII("ℲU+dd7aU+dd52。≯襾") V7 +Pass ToASCII("ⅎU+dd7aU+dd52。≯襾") V7 +Pass ToASCII("ⅎU+dd7aU+dd52。≯襾") V7 +Pass ToASCII("xn--73g39298c.xn--hdhz171b") V7 +Pass ToASCII("ⅎU+dd7aU+dd52。≯⾑") V7 +Pass ToASCII("ⅎU+dd7aU+dd52。≯⾑") V7 +Pass ToASCII("xn--f3g73398c.xn--hdhz171b") V7 +Pass ToASCII("‌.ßႩ-") C1; V3 (ignored) +Pass ToASCII("‌.ßⴉ-") C1; V3 (ignored) +Pass ToASCII("‌.SSႩ-") C1; V3 (ignored) +Pass ToASCII("‌.ssⴉ-") C1; V3 (ignored) +Pass ToASCII("‌.Ssⴉ-") C1; V3 (ignored) +Pass ToASCII(".xn--ss--bi1b") V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--ss--bi1b") C1; V3 (ignored) +Pass ToASCII("xn--0ug.xn----pfa2305a") C1; V3 (ignored) +Pass ToASCII(".xn--ss--4rn") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--ss--4rn") C1; V7; V3 (ignored) +Pass ToASCII("xn--0ug.xn----pfa042j") C1; V7; V3 (ignored) +Pass ToASCII("齙--U+dff0.ß") +Pass ToASCII("齙--4.ß") +Pass ToASCII("齙--4.SS") +Pass ToASCII("齙--4.ss") +Pass ToASCII("齙--4.Ss") +Pass ToASCII("xn----4-p16k.ss") +Pass ToASCII("xn----4-p16k.xn--zca") +Pass ToASCII("齙--U+dff0.SS") +Pass ToASCII("齙--U+dff0.ss") +Pass ToASCII("齙--U+dff0.Ss") +Pass ToASCII("U+dea2-。U+dc8f≮U+df2b") V7; V3 (ignored) +Pass ToASCII("U+dea2-。U+dc8f≮U+df2b") V7; V3 (ignored) +Pass ToASCII("xn----bh61m.xn--gdhz157g0em1d") V7; V3 (ignored) +Pass ToASCII("‌U+de79‍。U+dfe7≮Ⴉ") C1; C2; V7 +Pass ToASCII("‌U+de79‍。U+dfe7≮Ⴉ") C1; C2; V7 +Pass ToASCII("‌U+de79‍。U+dfe7≮ⴉ") C1; C2; V7 +Pass ToASCII("‌U+de79‍。U+dfe7≮ⴉ") C1; C2; V7 +Pass ToASCII("xn--3n36e.xn--gdh992byu01p") V7 +Pass ToASCII("xn--0ugc90904y.xn--gdh992byu01p") C1; C2; V7 +Pass ToASCII("xn--3n36e.xn--hnd112gpz83n") V7 +Pass ToASCII("xn--0ugc90904y.xn--hnd112gpz83n") C1; C2; V7 +Pass ToASCII("U+de9eႰ。쪡") V6 +Pass ToASCII("U+de9eႰ。쪡") V6 +Pass ToASCII("U+de9eႰ。쪡") V6 +Pass ToASCII("U+de9eႰ。쪡") V6 +Pass ToASCII("U+de9eⴐ。쪡") V6 +Pass ToASCII("U+de9eⴐ。쪡") V6 +Pass ToASCII("xn--7kj1858k.xn--pi6b") V6 +Pass ToASCII("U+de9eⴐ。쪡") V6 +Pass ToASCII("U+de9eⴐ。쪡") V6 +Pass ToASCII("xn--ond3755u.xn--pi6b") V6; V7 +Pass ToASCII("ᡅ0‌。⎢U+de04") C1; V7 +Pass ToASCII("ᡅ0‌。⎢U+de04") C1; V7 +Pass ToASCII("xn--0-z6j.xn--8lh28773l") V7 +Pass ToASCII("xn--0-z6jy93b.xn--8lh28773l") C1; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍ß") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍ß") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍SS") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍ss") C2; V7 +Pass ToASCII("xn--9-i0j5967eg3qz.ss") V7 +Pass ToASCII("xn--9-i0j5967eg3qz.xn--ss-l1t") C2; V7 +Pass ToASCII("xn--9-i0j5967eg3qz.xn--zca770n") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍SS") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍ss") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍Ss") C2; V7 +Pass ToASCII("U+df9a9ꍩ៓.‍Ss") C2; V7 +Pass ToASCII("ꗷU+dd80.ݝU+de52") +Pass ToASCII("xn--ju8a625r.xn--hpb0073k") +Pass ToASCII("⒐≯-。︒U+dc63-U+dee0") V7; V3 (ignored) +Pass ToASCII("⒐≯-。︒U+dc63-U+dee0") V7; V3 (ignored) +Pass ToASCII("9.≯-。。U+dc63-U+dee0") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("9.≯-。。U+dc63-U+dee0") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("9.xn----ogo..xn----xj54d1s69k") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----ogot9g.xn----n89hl0522az9u2a") V7; V3 (ignored) +Pass ToASCII("ႯU+dd4b-.‍Ⴉ") C2; V3 (ignored) +Pass ToASCII("ႯU+dd4b-.‍Ⴉ") C2; V3 (ignored) +Pass ToASCII("ⴏU+dd4b-.‍ⴉ") C2; V3 (ignored) +Pass ToASCII("xn----3vs.xn--0kj") V3 (ignored) +Pass ToASCII("xn----3vs.xn--1ug532c") C2; V3 (ignored) +Pass ToASCII("ⴏU+dd4b-.‍ⴉ") C2; V3 (ignored) +Pass ToASCII("xn----00g.xn--hnd") V7; V3 (ignored) +Pass ToASCII("xn----00g.xn--hnd399e") C2; V7; V3 (ignored) +Pass ToASCII("᜔。U+dda3-U+deea") V6; V3 (ignored) +Pass ToASCII("xn--fze.xn----ly8i") V6; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽß") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽß") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽSS") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽss") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽSs") V6; V7; V3 (ignored) +Pass ToASCII("xn----pw5e.xn--ss-7jd10716y") V6; V7; V3 (ignored) +Pass ToASCII("xn----pw5e.xn--zca50wfv060a") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽSS") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽss") V6; V7; V3 (ignored) +Pass ToASCII("ꯨ-.U+dfdcֽSs") V6; V7; V3 (ignored) +Pass ToASCII("U+dfe5♮U+df2b࣭.្U+df2b8U+dd8f") V6 +Pass ToASCII("3♮U+df2b࣭.្U+df2b8U+dd8f") V6 +Pass ToASCII("xn--3-ksd277tlo7s.xn--8-f0jx021l") V6 +Pass ToASCII("-。U+df00‍❡") C2; V7; V3 (ignored) +Pass ToASCII("-。U+df00‍❡") C2; V7; V3 (ignored) +Pass ToASCII("-.xn--nei54421f") V7; V3 (ignored) +Pass ToASCII("-.xn--1ug800aq795s") C2; V7; V3 (ignored) +Pass ToASCII("U+dfd3☱U+dfd0U+dc35。U+deaeU+dc73") V6; V7 +Pass ToASCII("5☱2U+dc35。U+deaeU+dc73") V6; V7 +Pass ToASCII("xn--52-dwx47758j.xn--kd3hk431k") V6; V7 +Pass ToASCII("-.-├U+dda3") V7; V3 (ignored) +Pass ToASCII("-.xn----ukp70432h") V7; V3 (ignored) +Pass ToASCII("ς.ﷁU+df9b⒈") V7 +Pass ToASCII("ς.فميU+df9b1.") A4_2 (ignored) +Pass ToASCII("Σ.فميU+df9b1.") A4_2 (ignored) +Pass ToASCII("σ.فميU+df9b1.") A4_2 (ignored) +Pass ToASCII("xn--4xa.xn--1-gocmu97674d.") A4_2 (ignored) +Pass ToASCII("xn--3xa.xn--1-gocmu97674d.") A4_2 (ignored) +Pass ToASCII("Σ.ﷁU+df9b⒈") V7 +Pass ToASCII("σ.ﷁU+df9b⒈") V7 +Pass ToASCII("xn--4xa.xn--dhbip2802atb20c") V7 +Pass ToASCII("xn--3xa.xn--dhbip2802atb20c") V7 +Pass ToASCII("9U+dde5.U+dd34ᢓ") V7 +Pass ToASCII("9U+dde5.U+dd34ᢓ") V7 +Pass ToASCII("9.xn--dbf91222q") V7 +Pass ToASCII("︒Ⴖͦ.‌") C1; V7 +Pass ToASCII("。Ⴖͦ.‌") C1; A4_2 (ignored) +Pass ToASCII("。ⴖͦ.‌") C1; A4_2 (ignored) +Pass ToASCII(".xn--hva754s.") A4_2 (ignored) +Pass ToASCII(".xn--hva754s.xn--0ug") C1; A4_2 (ignored) +Pass ToASCII("︒ⴖͦ.‌") C1; V7 +Pass ToASCII("xn--hva754sy94k.") V7; A4_2 (ignored) +Pass ToASCII("xn--hva754sy94k.xn--0ug") C1; V7 +Pass ToASCII(".xn--hva929d.") V7; A4_2 (ignored) +Pass ToASCII(".xn--hva929d.xn--0ug") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--hva929dl29p.") V7; A4_2 (ignored) +Pass ToASCII("xn--hva929dl29p.xn--0ug") C1; V7 +Pass ToASCII("xn--hva754s.") A4_2 (ignored) +Pass ToASCII("ⴖͦ.") A4_2 (ignored) +Pass ToASCII("Ⴖͦ.") A4_2 (ignored) +Pass ToASCII("xn--hva929d.") V7; A4_2 (ignored) +Pass ToASCII("xn--hzb.xn--ukj4430l") +Pass ToASCII("ࢻ.ⴃU+dc12") +Pass ToASCII("ࢻ.ႣU+dc12") +Pass ToASCII("xn--hzb.xn--bnd2938u") V7 +Pass ToASCII("‍‌。2䫷U+ddf7") C1; C2; V7 +Pass ToASCII("‍‌。2䫷U+ddf7") C1; C2; V7 +Pass ToASCII(".xn--2-me5ay1273i") V7; A4_2 (ignored) +Pass ToASCII("xn--0ugb.xn--2-me5ay1273i") C1; C2; V7 +Pass ToASCII("-U+dc24U+dc10。U+df16") V7; V3 (ignored) +Pass ToASCII("xn----rq4re4997d.xn--l707b") V7; V3 (ignored) +Pass ToASCII("U+dec2︒‌㟀.ؤ⒈") C1; V7 +Pass ToASCII("U+dec2︒‌㟀.ؤ⒈") C1; V7 +Pass ToASCII("xn--z272f.xn--etl.xn--1-smc.") V7; A4_2 (ignored) +Pass ToASCII("xn--etlt457ccrq7h.xn--jgb476m") V7 +Pass ToASCII("xn--0ug754gxl4ldlt0k.xn--jgb476m") C1; V7 +Pass ToASCII("߼U+de06.U+dd8f︒U+de29Ⴐ") V7 +Pass ToASCII("߼U+de06.U+dd8f。U+de29Ⴐ") V7 +Pass ToASCII("߼U+de06.U+dd8f。U+de29ⴐ") V7 +Pass ToASCII("xn--0tb8725k.xn--tu8d.xn--7kj73887a") V7 +Pass ToASCII("߼U+de06.U+dd8f︒U+de29ⴐ") V7 +Pass ToASCII("xn--0tb8725k.xn--7kj9008dt18a7py9c") V7 +Pass ToASCII("xn--0tb8725k.xn--tu8d.xn--ond97931d") V7 +Pass ToASCII("xn--0tb8725k.xn--ond3562jt18a7py9c") V7 +Pass ToASCII("Ⴥ⚭U+ddab⋃。U+df3c") V6; V7 +Pass ToASCII("Ⴥ⚭U+ddab⋃。U+df3c") V6; V7 +Pass ToASCII("ⴥ⚭U+ddab⋃。U+df3c") V6; V7 +Pass ToASCII("xn--vfh16m67gx1162b.xn--ro1d") V6; V7 +Pass ToASCII("ⴥ⚭U+ddab⋃。U+df3c") V6; V7 +Pass ToASCII("xn--9nd623g4zc5z060c.xn--ro1d") V6; V7 +Pass ToASCII("U+dd93⛏-。ꡒ") V3 (ignored) +Pass ToASCII("xn----o9p.xn--rc9a") V3 (ignored) +Pass ToASCII("U+de26帷。≯萺᷈-") V7; V3 (ignored) +Pass ToASCII("U+de26帷。≯萺᷈-") V7; V3 (ignored) +Pass ToASCII("U+de26帷。≯萺᷈-") V7; V3 (ignored) +Pass ToASCII("U+de26帷。≯萺᷈-") V7; V3 (ignored) +Pass ToASCII("xn--qutw175s.xn----mimu6tf67j") V7; V3 (ignored) +Pass ToASCII("‍攌꯭。ᢖ-Ⴘ") C2 +Pass ToASCII("‍攌꯭。ᢖ-ⴘ") C2 +Pass ToASCII("xn--p9ut19m.xn----mck373i") +Pass ToASCII("攌꯭.ᢖ-ⴘ") +Pass ToASCII("攌꯭.ᢖ-Ⴘ") +Pass ToASCII("xn--1ug592ykp6b.xn----mck373i") C2 +Pass ToASCII("xn--p9ut19m.xn----k1g451d") V7 +Pass ToASCII("xn--1ug592ykp6b.xn----k1g451d") C2; V7 +Pass ToASCII("‌ꖨ.⒗3툒۳") C1; V7 +Pass ToASCII("‌ꖨ.⒗3툒۳") C1; V7 +Pass ToASCII("‌ꖨ.16.3툒۳") C1 +Pass ToASCII("‌ꖨ.16.3툒۳") C1 +Pass ToASCII("xn--9r8a.16.xn--3-nyc0117m") +Pass ToASCII("ꖨ.16.3툒۳") +Pass ToASCII("ꖨ.16.3툒۳") +Pass ToASCII("xn--0ug2473c.16.xn--3-nyc0117m") C1 +Pass ToASCII("xn--9r8a.xn--3-nyc678tu07m") V7 +Pass ToASCII("xn--0ug2473c.xn--3-nyc678tu07m") C1; V7 +Pass ToASCII("U+dfcfU+de19⸖.‍") C2 +Pass ToASCII("1U+de19⸖.‍") C2 +Pass ToASCII("xn--1-5bt6845n.") A4_2 (ignored) +Pass ToASCII("1U+de19⸖.") A4_2 (ignored) +Pass ToASCII("xn--1-5bt6845n.xn--1ug") C2 +Pass ToASCII("FU+dd5f。U+ddc5♚") V7 +Pass ToASCII("FU+dd5f。U+ddc5♚") V7 +Pass ToASCII("fU+dd5f。U+ddc5♚") V7 +Pass ToASCII("f.xn--45hz6953f") V7 +Pass ToASCII("fU+dd5f。U+ddc5♚") V7 +Pass ToASCII("୍U+dd34ᷩ。U+dfeeႸU+dc28U+dd47") V6; V7 +Pass ToASCII("୍U+dd34ᷩ。2ႸU+dc28U+dd47") V6; V7 +Pass ToASCII("୍U+dd34ᷩ。2ⴘU+dc28U+dd47") V6; V7 +Pass ToASCII("xn--9ic246gs21p.xn--2-nws2918ndrjr") V6; V7 +Pass ToASCII("୍U+dd34ᷩ。U+dfeeⴘU+dc28U+dd47") V6; V7 +Pass ToASCII("xn--9ic246gs21p.xn--2-k1g43076adrwq") V6; V7 +Pass ToASCII("U+dc2d‌‌⒈。勉U+dc45") C1; V7 +Pass ToASCII("U+dc2d‌‌1.。勉U+dc45") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--1-yi00h..xn--4grs325b") V7; A4_2 (ignored) +Pass ToASCII("xn--1-rgna61159u..xn--4grs325b") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--tsh11906f.xn--4grs325b") V7 +Pass ToASCII("xn--0uga855aez302a.xn--4grs325b") C1; V7 +Pass ToASCII("ᡃ.玿U+de1cU+df90") V7 +Pass ToASCII("xn--27e.xn--7cy81125a0yq4a") V7 +Pass ToASCII("‌‌。⒈≯U+dff5") C1; V7 +Pass ToASCII("‌‌。⒈≯U+dff5") C1; V7 +Pass ToASCII("‌‌。1.≯9") C1 +Pass ToASCII("‌‌。1.≯9") C1 +Pass ToASCII(".1.xn--9-ogo") A4_2 (ignored) +Pass ToASCII("xn--0uga.1.xn--9-ogo") C1 +Pass ToASCII(".xn--9-ogo37g") V7; A4_2 (ignored) +Pass ToASCII("xn--0uga.xn--9-ogo37g") C1; V7 +Pass ToASCII("⃚.U+de3f-") V6; V3 (ignored) +Pass ToASCII("⃚.U+de3f-") V6; V3 (ignored) +Pass ToASCII("xn--w0g.xn----bd0j") V6; V3 (ignored) +Pass ToASCII("ႂ-‍꣪.ꡊ‍U+de33") C2; V6; V7 +Pass ToASCII("ႂ-‍꣪.ꡊ‍U+de33") C2; V6; V7 +Pass ToASCII("xn----gyg3618i.xn--jc9ao4185a") V6; V7 +Pass ToASCII("xn----gyg250jio7k.xn--1ug8774cri56d") C2; V6; V7 +Pass ToASCII("U+de35廊.U+dc0d") V6 +Pass ToASCII("xn--xytw701b.xn--yc9c") V6 +Pass ToASCII("ႾU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("ႾU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("ႾU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("ႾU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("ⴞU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("ⴞU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("xn--mlj0486jgl2j.xn--hbf6853f") V7 +Pass ToASCII("ⴞU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("ⴞU+dec0U+ddfb.ᢗ릫") V7 +Pass ToASCII("xn--2nd8876sgl2j.xn--hbf6853f") V7 +Pass ToASCII("ß‍်。⒈") C2; V7 +Pass ToASCII("xn--ss-f4j.b.") A4_2 (ignored) +Pass ToASCII("ss်.b.") A4_2 (ignored) +Pass ToASCII("SS်.B.") A4_2 (ignored) +Pass ToASCII("Ss်.b.") A4_2 (ignored) +Pass ToASCII("xn--ss-f4j585j.b.") C2; A4_2 (ignored) +Pass ToASCII("xn--zca679eh2l.b.") C2; A4_2 (ignored) +Pass ToASCII("SS‍်。⒈") C2; V7 +Pass ToASCII("ss‍်。⒈") C2; V7 +Pass ToASCII("Ss‍်。⒈") C2; V7 +Pass ToASCII("xn--ss-f4j.xn--tsh") V7 +Pass ToASCII("xn--ss-f4j585j.xn--tsh") C2; V7 +Pass ToASCII("xn--zca679eh2l.xn--tsh") C2; V7 +Pass ToASCII("SS်.b.") A4_2 (ignored) +Pass ToASCII("یU+de3f.ß྄U+df6c") +Pass ToASCII("یU+de3f.ß྄U+df6c") +Pass ToASCII("یU+de3f.SS྄U+df6c") +Pass ToASCII("یU+de3f.ss྄U+df6c") +Pass ToASCII("xn--clb2593k.xn--ss-toj6092t") +Pass ToASCII("xn--clb2593k.xn--zca216edt0r") +Pass ToASCII("یU+de3f.SS྄U+df6c") +Pass ToASCII("یU+de3f.ss྄U+df6c") +Pass ToASCII("یU+de3f.Ss྄U+df6c") +Pass ToASCII("یU+de3f.Ss྄U+df6c") +Pass ToASCII("U+dfe0≮‌。U+dd71឴") C1; A4_2 (ignored) +Pass ToASCII("U+dfe0≮‌。U+dd71឴") C1; A4_2 (ignored) +Pass ToASCII("8≮‌。U+dd71឴") C1; A4_2 (ignored) +Pass ToASCII("8≮‌。U+dd71឴") C1; A4_2 (ignored) +Pass ToASCII("xn--8-ngo.") A4_2 (ignored) +Pass ToASCII("8≮.") A4_2 (ignored) +Pass ToASCII("8≮.") A4_2 (ignored) +Pass ToASCII("xn--8-sgn10i.") C1; A4_2 (ignored) +Pass ToASCII("xn--8-ngo.xn--z3e") V6; V7 +Pass ToASCII("xn--8-sgn10i.xn--z3e") C1; V6; V7 +Pass ToASCII("ᢕ≯︒U+dcaf.Ⴀ") V7 +Pass ToASCII("ᢕ≯︒U+dcaf.Ⴀ") V7 +Pass ToASCII("ᢕ≯。U+dcaf.Ⴀ") V7 +Pass ToASCII("ᢕ≯。U+dcaf.Ⴀ") V7 +Pass ToASCII("ᢕ≯。U+dcaf.ⴀ") V7 +Pass ToASCII("ᢕ≯。U+dcaf.ⴀ") V7 +Pass ToASCII("xn--fbf851c.xn--ko1u.xn--rkj") V7 +Pass ToASCII("ᢕ≯︒U+dcaf.ⴀ") V7 +Pass ToASCII("ᢕ≯︒U+dcaf.ⴀ") V7 +Pass ToASCII("xn--fbf851cq98poxw1a.xn--rkj") V7 +Pass ToASCII("xn--fbf851c.xn--ko1u.xn--7md") V7 +Pass ToASCII("xn--fbf851cq98poxw1a.xn--7md") V7 +Pass ToASCII("ྟ.-ࠪ") V6; V3 (ignored) +Pass ToASCII("ྟ.-ࠪ") V6; V3 (ignored) +Pass ToASCII("xn--vfd.xn----fhd") V6; V3 (ignored) +Pass ToASCII("ᵬU+dda0.핒⒒⒈U+dd26") V7 +Pass ToASCII("ᵬU+dda0.핒⒒⒈U+dd26") V7 +Pass ToASCII("ᵬU+dda0.핒11.1.U+dd26") V7 +Pass ToASCII("ᵬU+dda0.핒11.1.U+dd26") V7 +Pass ToASCII("xn--tbg.xn--11-5o7k.1.xn--k469f") V7 +Pass ToASCII("xn--tbg.xn--tsht7586kyts9l") V7 +Pass ToASCII("⒈✌U+df1f.U+dfe1U+dc63") V7 +Pass ToASCII("1.✌U+df1f.9U+dc63") V7 +Pass ToASCII("1.xn--7bi44996f.xn--9-o706d") V7 +Pass ToASCII("xn--tsh24g49550b.xn--9-o706d") V7 +Pass ToASCII("ς.꧀꣄") V6 +Pass ToASCII("ς.꧀꣄") V6 +Pass ToASCII("Σ.꧀꣄") V6 +Pass ToASCII("σ.꧀꣄") V6 +Pass ToASCII("xn--4xa.xn--0f9ars") V6 +Pass ToASCII("xn--3xa.xn--0f9ars") V6 +Pass ToASCII("Σ.꧀꣄") V6 +Pass ToASCII("σ.꧀꣄") V6 +Pass ToASCII("羚。≯") +Pass ToASCII("羚。≯") +Pass ToASCII("羚。≯") +Pass ToASCII("羚。≯") +Pass ToASCII("xn--xt0a.xn--hdh") +Pass ToASCII("羚.≯") +Pass ToASCII("羚.≯") +Pass ToASCII("➆U+ddd5ỗ⒈.U+df12U+de2e࡛U+dfeb") V7 +Pass ToASCII("➆U+ddd5ỗ⒈.U+df12U+de2e࡛U+dfeb") V7 +Pass ToASCII("➆U+ddd5ỗ1..U+df12U+de2e࡛9") V7; A4_2 (ignored) +Pass ToASCII("➆U+ddd5ỗ1..U+df12U+de2e࡛9") V7; A4_2 (ignored) +Pass ToASCII("➆U+ddd5Ỗ1..U+df12U+de2e࡛9") V7; A4_2 (ignored) +Pass ToASCII("➆U+ddd5Ỗ1..U+df12U+de2e࡛9") V7; A4_2 (ignored) +Pass ToASCII("xn--1-3xm292b6044r..xn--9-6jd87310jtcqs") V7; A4_2 (ignored) +Pass ToASCII("➆U+ddd5Ỗ⒈.U+df12U+de2e࡛U+dfeb") V7 +Pass ToASCII("➆U+ddd5Ỗ⒈.U+df12U+de2e࡛U+dfeb") V7 +Pass ToASCII("xn--6lg26tvvc6v99z.xn--9-6jd87310jtcqs") V7 +Pass ToASCII(".xn--ye6h") A4_2 (ignored) +Pass ToASCII("xn--ye6h") +Pass ToASCII("U+dd3a") +Pass ToASCII("U+dd18") +Pass ToASCII("ܼ‌-。U+dc3eß") C1; V6; V7; V3 (ignored) +Pass ToASCII("ܼ‌-。U+dc3eSS") C1; V6; V7; V3 (ignored) +Pass ToASCII("ܼ‌-。U+dc3ess") C1; V6; V7; V3 (ignored) +Pass ToASCII("ܼ‌-。U+dc3eSs") C1; V6; V7; V3 (ignored) +Pass ToASCII("xn----s2c.xn--ss-066q") V6; V7; V3 (ignored) +Pass ToASCII("xn----s2c071q.xn--ss-066q") C1; V6; V7; V3 (ignored) +Pass ToASCII("xn----s2c071q.xn--zca7848m") C1; V6; V7; V3 (ignored) +Pass ToASCII("-U+df6c፞U+df27.ᷫ-︒") V6; V7; V3 (ignored) +Pass ToASCII("-U+df6c፞U+df27.ᷫ-。") V6; V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----b5h1837n2ok9f.xn----mkm.") V6; V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----b5h1837n2ok9f.xn----mkmw278h") V6; V7; V3 (ignored) +Pass ToASCII("︒.U+dc21ᩙ") V7 +Pass ToASCII("。.U+dc21ᩙ") V7; A4_2 (ignored) +Pass ToASCII("..xn--cof61594i") V7; A4_2 (ignored) +Pass ToASCII("xn--y86c.xn--cof61594i") V7 +Pass ToASCII("U+dc3a.-U+dfcf") V6; V7; V3 (ignored) +Pass ToASCII("xn--jk3d.xn----iz68g") V6; V7; V3 (ignored) +Pass ToASCII("U+dee9.赏") V7 +Pass ToASCII("U+dee9.赏") V7 +Pass ToASCII("xn--2856e.xn--6o3a") V7 +Pass ToASCII("Ⴍ.U+dde6‌") C1; V7 +Pass ToASCII("Ⴍ.U+dde6‌") C1; V7 +Pass ToASCII("ⴍ.U+dde6‌") C1; V7 +Pass ToASCII("xn--4kj.xn--p01x") V7 +Pass ToASCII("xn--4kj.xn--0ug56448b") C1; V7 +Pass ToASCII("ⴍ.U+dde6‌") C1; V7 +Pass ToASCII("xn--lnd.xn--p01x") V7 +Pass ToASCII("xn--lnd.xn--0ug56448b") C1; V7 +Pass ToASCII("U+dfdb.笠") +Pass ToASCII("U+dfdb.笠") +Pass ToASCII("3.笠") +Pass ToASCII("3.xn--6vz") +Pass ToASCII("-‍.ႾU+def7") C2; V3 (ignored) +Pass ToASCII("-‍.ⴞU+def7") C2; V3 (ignored) +Pass ToASCII("-.xn--mlj8559d") V3 (ignored) +Pass ToASCII("xn----ugn.xn--mlj8559d") C2; V3 (ignored) +Pass ToASCII("-.xn--2nd2315j") V7; V3 (ignored) +Pass ToASCII("xn----ugn.xn--2nd2315j") C2; V7; V3 (ignored) +Pass ToASCII("‍ςßܱ.்") C2; V6 +Pass ToASCII("‍ςßܱ.்") C2; V6 +Pass ToASCII("‍ΣSSܱ.்") C2; V6 +Pass ToASCII("‍σssܱ.்") C2; V6 +Pass ToASCII("‍Σssܱ.்") C2; V6 +Pass ToASCII("xn--ss-ubc826a.xn--xmc") V6 +Pass ToASCII("xn--ss-ubc826ab34b.xn--xmc") C2; V6 +Pass ToASCII("‍Σßܱ.்") C2; V6 +Pass ToASCII("‍σßܱ.்") C2; V6 +Pass ToASCII("xn--zca39lk1di19a.xn--xmc") C2; V6 +Pass ToASCII("xn--zca19ln1di19a.xn--xmc") C2; V6 +Pass ToASCII("‍ΣSSܱ.்") C2; V6 +Pass ToASCII("‍σssܱ.்") C2; V6 +Pass ToASCII("‍Σssܱ.்") C2; V6 +Pass ToASCII("‍Σßܱ.்") C2; V6 +Pass ToASCII("‍σßܱ.்") C2; V6 +Pass ToASCII("≠.‍") C2 +Pass ToASCII("≠.‍") C2 +Pass ToASCII("≠.‍") C2 +Pass ToASCII("≠.‍") C2 +Pass ToASCII("xn--1ch.") A4_2 (ignored) +Pass ToASCII("≠.") A4_2 (ignored) +Pass ToASCII("≠.") A4_2 (ignored) +Pass ToASCII("xn--1ch.xn--1ug") C2 +Pass ToASCII("U+def5্ς.ςU+de3f") V7 +Pass ToASCII("U+def5্ς.ςU+de3f") V7 +Pass ToASCII("U+def5্Σ.ΣU+de3f") V7 +Pass ToASCII("U+def5্σ.ςU+de3f") V7 +Pass ToASCII("U+def5্σ.σU+de3f") V7 +Pass ToASCII("U+def5্Σ.σU+de3f") V7 +Pass ToASCII("xn--4xa502av8297a.xn--4xa6055k") V7 +Pass ToASCII("U+def5্Σ.ςU+de3f") V7 +Pass ToASCII("xn--4xa502av8297a.xn--3xa8055k") V7 +Pass ToASCII("xn--3xa702av8297a.xn--3xa8055k") V7 +Pass ToASCII("U+def5্Σ.ΣU+de3f") V7 +Pass ToASCII("U+def5্σ.ςU+de3f") V7 +Pass ToASCII("U+def5্σ.σU+de3f") V7 +Pass ToASCII("U+def5্Σ.σU+de3f") V7 +Pass ToASCII("U+def5্Σ.ςU+de3f") V7 +Pass ToASCII("U+dd12。륧") V7 +Pass ToASCII("U+dd12。륧") V7 +Pass ToASCII("U+dd12。륧") V7 +Pass ToASCII("U+dd12。륧") V7 +Pass ToASCII("xn--s264a.xn--pw2b") V7 +Pass ToASCII("ᡆU+dcdd.U+dd46") V7 +Pass ToASCII("ᡆU+dcdd.U+dd46") V7 +Pass ToASCII("xn--57e0440k.xn--k86h") V7 +Pass ToASCII("U+dfe6。ᠽ") V7 +Pass ToASCII("U+dfe6。ᠽ") V7 +Pass ToASCII("xn--j890g.xn--w7e") V7 +Pass ToASCII("嬃U+df4c.‍ୄ") C2 +Pass ToASCII("嬃U+df4c.‍ୄ") C2 +Pass ToASCII("xn--b6s0078f.xn--0ic") V6 +Pass ToASCII("xn--b6s0078f.xn--0ic557h") C2 +Pass ToASCII("‌.U+dee4") C1; V7 +Pass ToASCII(".xn--q823a") V7; A4_2 (ignored) +Pass ToASCII("xn--0ug.xn--q823a") C1; V7 +Pass ToASCII("U+ded5Ⴃ䠅.U+de11") V7 +Pass ToASCII("U+ded5Ⴃ䠅.U+de11") V7 +Pass ToASCII("U+ded5ⴃ䠅.U+de11") V7 +Pass ToASCII("xn--ukju77frl47r.xn--yl0d") V7 +Pass ToASCII("U+ded5ⴃ䠅.U+de11") V7 +Pass ToASCII("xn--bnd074zr557n.xn--yl0d") V7 +Pass ToASCII("-。︒") V7; V3 (ignored) +Pass ToASCII("-。。") V3 (ignored); A4_2 (ignored) +Pass ToASCII("-..") V3 (ignored); A4_2 (ignored) +Pass ToASCII("-.xn--y86c") V7; V3 (ignored) +Pass ToASCII("‍.F") C2 +Pass ToASCII("‍.f") C2 +Pass ToASCII(".f") A4_2 (ignored) +Pass ToASCII("xn--1ug.f") C2 +Pass ToASCII("f") +Pass ToASCII("‍㨲。ß") C2 +Pass ToASCII("‍㨲。ß") C2 +Pass ToASCII("‍㨲。SS") C2 +Pass ToASCII("‍㨲。ss") C2 +Pass ToASCII("‍㨲。Ss") C2 +Pass ToASCII("xn--9bm.ss") +Pass ToASCII("㨲.ss") +Pass ToASCII("㨲.SS") +Pass ToASCII("㨲.Ss") +Pass ToASCII("xn--1ug914h.ss") C2 +Pass ToASCII("xn--1ug914h.xn--zca") C2 +Pass ToASCII("‍㨲。SS") C2 +Pass ToASCII("‍㨲。ss") C2 +Pass ToASCII("‍㨲。Ss") C2 +Pass ToASCII("‍.U+de28") C2; V7 +Pass ToASCII("‍.U+de28") C2; V7 +Pass ToASCII(".xn--h327f") V7; A4_2 (ignored) +Pass ToASCII("xn--1ug.xn--h327f") C2; V7 +Pass ToASCII("U+df7bU+dd41。≠U+dff2") V7 +Pass ToASCII("U+df7bU+dd41。≠U+dff2") V7 +Pass ToASCII("U+df7bU+dd41。≠6") V7 +Pass ToASCII("U+df7bU+dd41。≠6") V7 +Pass ToASCII("xn--h79w4z99a.xn--6-tfo") V7 +Pass ToASCII("xn--98e.xn--om9c") V7 +Pass ToASCII("꫶ᢏฺ2.U+dee2݅ྟ︒") V6; V7 +Pass ToASCII("꫶ᢏฺ2.U+dee2݅ྟ。") V6; A4_2 (ignored) +Pass ToASCII("xn--2-2zf840fk16m.xn--sob093b2m7s.") V6; A4_2 (ignored) +Pass ToASCII("xn--2-2zf840fk16m.xn--sob093bj62sz9d") V6; V7 +Pass ToASCII("U+dd27。≠-U+de44⾛") V7 +Pass ToASCII("U+dd27。≠-U+de44⾛") V7 +Pass ToASCII("U+dd27。≠-U+de44走") V7 +Pass ToASCII("U+dd27。≠-U+de44走") V7 +Pass ToASCII("xn--gm57d.xn----tfo4949b3664m") V7 +Pass ToASCII("U+dfce。甯") +Pass ToASCII("0。甯") +Pass ToASCII("0.xn--qny") +Pass ToASCII("0.甯") +Pass ToASCII("-⾆.꫶") V6; V3 (ignored) +Pass ToASCII("-舌.꫶") V6; V3 (ignored) +Pass ToASCII("xn----ef8c.xn--2v9a") V6; V3 (ignored) +Pass ToASCII("-。ᢘ") V3 (ignored) +Pass ToASCII("-。ᢘ") V3 (ignored) +Pass ToASCII("-.xn--ibf") V3 (ignored) +Pass ToASCII("U+dcb4Ⴋ.≮") +Pass ToASCII("U+dcb4Ⴋ.≮") +Pass ToASCII("U+dcb4ⴋ.≮") +Pass ToASCII("U+dcb4ⴋ.≮") +Pass ToASCII("xn--2kj7565l.xn--gdh") +Pass ToASCII("xn--jnd1986v.xn--gdh") V7 +Pass ToASCII("璼U+de2d。‌U+dddf") C1 +Pass ToASCII("璼U+de2d。‌U+dddf") C1 +Pass ToASCII("xn--gky8837e.") A4_2 (ignored) +Pass ToASCII("璼U+de2d.") A4_2 (ignored) +Pass ToASCII("xn--gky8837e.xn--0ug") C1 +Pass ToASCII("‌.‌") C1 +Pass ToASCII("xn--0ug.xn--0ug") C1 +Pass ToASCII("xn--157b.xn--gnb") +Pass ToASCII("튛.ܖ") +Pass ToASCII("튛.ܖ") +Pass ToASCII("Ⴗ.ׂU+dd34ꦷU+dce8") V6; V7 +Pass ToASCII("Ⴗ.U+dd34ׂꦷU+dce8") V6; V7 +Pass ToASCII("Ⴗ.U+dd34ׂꦷU+dce8") V6; V7 +Pass ToASCII("ⴗ.U+dd34ׂꦷU+dce8") V6; V7 +Pass ToASCII("xn--flj.xn--qdb0605f14ycrms3c") V6; V7 +Pass ToASCII("ⴗ.U+dd34ׂꦷU+dce8") V6; V7 +Pass ToASCII("ⴗ.ׂU+dd34ꦷU+dce8") V6; V7 +Pass ToASCII("xn--vnd.xn--qdb0605f14ycrms3c") V6; V7 +Pass ToASCII("⒈酫︒。ࣖ") V6; V7 +Pass ToASCII("1.酫。。ࣖ") V6; A4_2 (ignored) +Pass ToASCII("1.xn--8j4a..xn--8zb") V6; A4_2 (ignored) +Pass ToASCII("xn--tsh4490bfe8c.xn--8zb") V6; V7 +Pass ToASCII("ⷣ‌≮ᩫ.‌ฺ") C1; V6 +Pass ToASCII("ⷣ‌≮ᩫ.‌ฺ") C1; V6 +Pass ToASCII("xn--uof548an0j.xn--o4c") V6 +Pass ToASCII("xn--uof63xk4bf3s.xn--o4c732g") C1; V6 +Pass ToASCII("xn--co6h.xn--1-kwssa") V7 +Pass ToASCII("xn--co6h.xn--1-h1g429s") V7 +Pass ToASCII("xn--co6h.xn--1-h1gs") V7 +Pass ToASCII("꠆。U+de8fྰ⒕") V6; V7 +Pass ToASCII("꠆。U+de8fྰ14.") V6; V7; A4_2 (ignored) +Pass ToASCII("xn--l98a.xn--14-jsj57880f.") V6; V7; A4_2 (ignored) +Pass ToASCII("xn--l98a.xn--dgd218hhp28d") V6; V7 +Pass ToASCII("U+dfe04U+ddd7U+de3b.‍U+def5⛧‍") C2 +Pass ToASCII("84U+ddd7U+de3b.‍U+def5⛧‍") C2 +Pass ToASCII("xn--84-s850a.xn--59h6326e") +Pass ToASCII("84U+de3b.U+def5⛧") +Pass ToASCII("xn--84-s850a.xn--1uga573cfq1w") C2 +Pass ToASCII("≮U+dfd5.謖ß≯") +Pass ToASCII("≮U+dfd5.謖ß≯") +Pass ToASCII("≮7.謖ß≯") +Pass ToASCII("≮7.謖ß≯") +Pass ToASCII("≮7.謖SS≯") +Pass ToASCII("≮7.謖SS≯") +Pass ToASCII("≮7.謖ss≯") +Pass ToASCII("≮7.謖ss≯") +Pass ToASCII("≮7.謖Ss≯") +Pass ToASCII("≮7.謖Ss≯") +Pass ToASCII("xn--7-mgo.xn--ss-xjvv174c") +Pass ToASCII("xn--7-mgo.xn--zca892oly5e") +Pass ToASCII("≮U+dfd5.謖SS≯") +Pass ToASCII("≮U+dfd5.謖SS≯") +Pass ToASCII("≮U+dfd5.謖ss≯") +Pass ToASCII("≮U+dfd5.謖ss≯") +Pass ToASCII("≮U+dfd5.謖Ss≯") +Pass ToASCII("≮U+dfd5.謖Ss≯") +Pass ToASCII("U+df0e⒈。‌U+dfe4") C1; V7 +Pass ToASCII("U+df0e1.。‌2") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--1-ex54e..c") V7; A4_2 (ignored) +Pass ToASCII("xn--1-ex54e..xn--2-rgn") C1; V7; A4_2 (ignored) +Pass ToASCII("xn--tsh94183d.c") V7 +Pass ToASCII("xn--tsh94183d.xn--2-rgn") C1; V7 +Pass ToASCII("‍‌U+ddaa。ßU+dcc3") C1; C2 +Pass ToASCII("‍‌U+ddaa。ßU+dcc3") C1; C2 +Pass ToASCII("‍‌U+ddaa。SSU+dcc3") C1; C2 +Pass ToASCII("‍‌U+ddaa。ssU+dcc3") C1; C2 +Pass ToASCII("‍‌U+ddaa。SsU+dcc3") C1; C2 +Pass ToASCII(".xn--ss-bh7o") A4_2 (ignored) +Pass ToASCII("xn--0ugb.xn--ss-bh7o") C1; C2 +Pass ToASCII("xn--0ugb.xn--zca0732l") C1; C2 +Pass ToASCII("‍‌U+ddaa。SSU+dcc3") C1; C2 +Pass ToASCII("‍‌U+ddaa。ssU+dcc3") C1; C2 +Pass ToASCII("‍‌U+ddaa。SsU+dcc3") C1; C2 +Pass ToASCII("xn--ss-bh7o") +Pass ToASCII("ssU+dcc3") +Pass ToASCII("SSU+dcc3") +Pass ToASCII("SsU+dcc3") +Pass ToASCII("︒‌ヶ䒩.ꡪ") C1; V7 +Pass ToASCII("。‌ヶ䒩.ꡪ") C1; A4_2 (ignored) +Pass ToASCII(".xn--qekw60d.xn--gd9a") A4_2 (ignored) +Pass ToASCII(".xn--0ug287dj0o.xn--gd9a") C1; A4_2 (ignored) +Pass ToASCII("xn--qekw60dns9k.xn--gd9a") V7 +Pass ToASCII("xn--0ug287dj0or48o.xn--gd9a") C1; V7 +Pass ToASCII("xn--qekw60d.xn--gd9a") +Pass ToASCII("ヶ䒩.ꡪ") +Pass ToASCII("‌⒈U+df8d.U+dccb᩠") C1; V7 +Pass ToASCII("‌1.U+df8d.U+dccb᩠") C1; V7 +Pass ToASCII("1.xn--4x6j.xn--jof45148n") V7 +Pass ToASCII("xn--1-rgn.xn--4x6j.xn--jof45148n") C1; V7 +Pass ToASCII("xn--tshw462r.xn--jof45148n") V7 +Pass ToASCII("xn--0ug88o7471d.xn--jof45148n") C1; V7 +Pass ToASCII("U+dd75。U+dfebU+dc08䬺⒈") V7; A4_2 (ignored) +Pass ToASCII("U+dd75。9U+dc08䬺1.") A4_2 (ignored) +Pass ToASCII(".xn--91-030c1650n.") A4_2 (ignored) +Pass ToASCII(".xn--9-ecp936non25a") V7; A4_2 (ignored) +Pass ToASCII("xn--3f1h.xn--91-030c1650n.") V7; A4_2 (ignored) +Pass ToASCII("xn--3f1h.xn--9-ecp936non25a") V7 +Pass ToASCII("xn--8c1a.xn--2ib8jn539l") +Pass ToASCII("舛.ٽU+dd34ڻ") +Pass ToASCII("舛.ٽU+dd12ڻ") +Pass ToASCII("-U+dd710。៏᷽톇십") V6; V3 (ignored) +Pass ToASCII("-U+dd710。៏᷽톇십") V6; V3 (ignored) +Pass ToASCII("-U+dd710。៏᷽톇십") V6; V3 (ignored) +Pass ToASCII("-U+dd710。៏᷽톇십") V6; V3 (ignored) +Pass ToASCII("-0.xn--r4e872ah77nghm") V6; V3 (ignored) +Pass ToASCII("ᅟႿႵრ。୍") V6 +Pass ToASCII("ᅟႿႵრ。୍") V6 +Pass ToASCII("ᅟⴟⴕრ。୍") V6 +Pass ToASCII("ᅟႿႵᲠ。୍") V6 +Pass ToASCII("xn--1od555l3a.xn--9ic") V6 +Pass ToASCII("ᅟⴟⴕრ。୍") V6 +Pass ToASCII("ᅟႿႵᲠ。୍") V6 +Pass ToASCII("xn--tndt4hvw.xn--9ic") V6; V7 +Pass ToASCII("xn--1od7wz74eeb.xn--9ic") V6; V7 +Pass ToASCII("ᅟႿⴕრ。୍") V6 +Pass ToASCII("xn--3nd0etsm92g.xn--9ic") V6; V7 +Pass ToASCII("ᅟႿⴕრ。୍") V6 +Pass ToASCII("xn--l96h.xn--o8e4044k") V7 +Pass ToASCII("xn--l96h.xn--03e93aq365d") V7 +Pass ToASCII("U+dfdbU+ddaa꣄。꣪-") V6; V3 (ignored) +Pass ToASCII("U+dfdb꣄U+ddaa。꣪-") V6; V3 (ignored) +Pass ToASCII("3꣄U+ddaa。꣪-") V6; V3 (ignored) +Pass ToASCII("xn--3-sl4eu679e.xn----xn4e") V6; V3 (ignored) +Pass ToASCII("ᄹ。໊U+dfe4U+dd1e") V6; V7 +Pass ToASCII("ᄹ。໊U+dfe4U+dd1e") V6; V7 +Pass ToASCII("xn--lrd.xn--s8c05302k") V6; V7 +Pass ToASCII("ႦU+dca9.U+dda1︉U+dd0d") V7 +Pass ToASCII("ႦU+dca9.U+dda1︉U+dd0d") V7 +Pass ToASCII("ⴆU+dca9.U+dda1︉U+dd2f") V7 +Pass ToASCII("xn--xkjw3965g.xn--ne6h") V7 +Pass ToASCII("ⴆU+dca9.U+dda1︉U+dd2f") V7 +Pass ToASCII("xn--end82983m.xn--ne6h") V7 +Pass ToASCII("ⴆU+dca9.U+dda1︉U+dd0d") V7 +Pass ToASCII("ⴆU+dca9.U+dda1︉U+dd0d") V7 +Pass ToASCII("U+dee8.U+dfe2U+dfe8꣄") V7 +Pass ToASCII("U+dee8.U+dfe26꣄") V7 +Pass ToASCII("xn--mi60a.xn--6-sl4es8023c") V7 +Pass ToASCII("U+def8U+de0bჂ.Ⴁ") V7 +Pass ToASCII("U+def8U+de0bⴢ.ⴁ") V7 +Pass ToASCII("U+def8U+de0bჂ.ⴁ") V7 +Pass ToASCII("xn--qlj1559dr224h.xn--skj") V7 +Pass ToASCII("xn--6nd5215jr2u0h.xn--skj") V7 +Pass ToASCII("xn--6nd5215jr2u0h.xn--8md") V7 +Pass ToASCII("U+dc7f꠆₄U+df86。U+de67U+dcb9ς") V7 +Pass ToASCII("U+dc7f꠆4U+df86。U+de67U+dcb9ς") V7 +Pass ToASCII("U+dc7f꠆4U+df86。U+de67U+dcb9Σ") V7 +Pass ToASCII("U+dc7f꠆4U+df86。U+de67U+dcb9σ") V7 +Pass ToASCII("xn--4-w93ej7463a9io5a.xn--4xa31142bk3f0d") V7 +Pass ToASCII("xn--4-w93ej7463a9io5a.xn--3xa51142bk3f0d") V7 +Pass ToASCII("U+dc7f꠆₄U+df86。U+de67U+dcb9Σ") V7 +Pass ToASCII("U+dc7f꠆₄U+df86。U+de67U+dcb9σ") V7 +Pass ToASCII("U+dcac。ܩ。쯙5") V7 +Pass ToASCII("U+dcac。ܩ。쯙5") V7 +Pass ToASCII("xn--t92s.xn--znb.xn--5-y88f") V7 +Pass ToASCII("៊.‍U+dfeeU+dc3f") C2; V6 +Pass ToASCII("៊.‍2U+dc3f") C2; V6 +Pass ToASCII("xn--m4e.xn--2-ku7i") V6 +Pass ToASCII("xn--m4e.xn--2-tgnv469h") C2; V6 +Pass ToASCII("꫶。嬶ß葽") V6 +Pass ToASCII("꫶。嬶SS葽") V6 +Pass ToASCII("꫶。嬶ss葽") V6 +Pass ToASCII("꫶。嬶Ss葽") V6 +Pass ToASCII("xn--2v9a.xn--ss-q40dp97m") V6 +Pass ToASCII("xn--2v9a.xn--zca7637b14za") V6 +Pass ToASCII("ςU+dc3dU+dc88U+df2b。U+df29‌U+dec4") C1; V7 +Pass ToASCII("ςU+dc3dU+dc88U+df2b。U+df29‌U+dec4") C1; V7 +Pass ToASCII("ΣU+dc3dU+dc88U+df2b。U+df29‌U+dec4") C1; V7 +Pass ToASCII("σU+dc3dU+dc88U+df2b。U+df29‌U+dec4") C1; V7 +Pass ToASCII("xn--4xa2260lk3b8z15g.xn--tw9ct349a") V7 +Pass ToASCII("xn--4xa2260lk3b8z15g.xn--0ug4653g2xzf") C1; V7 +Pass ToASCII("xn--3xa4260lk3b8z15g.xn--0ug4653g2xzf") C1; V7 +Pass ToASCII("ΣU+dc3dU+dc88U+df2b。U+df29‌U+dec4") C1; V7 +Pass ToASCII("σU+dc3dU+dc88U+df2b。U+df29‌U+dec4") C1; V7 +Pass ToASCII("⺢U+de85U+dfe4。‍U+deb7") C2; V7 +Pass ToASCII("⺢U+de852。‍U+deb7") C2; V7 +Pass ToASCII("xn--2-4jtr4282f.xn--m78h") V7 +Pass ToASCII("xn--2-4jtr4282f.xn--1ugz946p") C2; V7 +Pass ToASCII("U+de25。⫟U+de3e") V6 +Pass ToASCII("xn--n82h.xn--63iw010f") V6 +Pass ToASCII("-ᢗ‌U+dd04.U+df22") C1; V6; V3 (ignored); U1 (ignored) +Pass ToASCII("-ᢗ‌3,.U+df22") C1; V6; V3 (ignored); U1 (ignored) +Pass ToASCII("xn---3,-3eu.xn--9h2d") V6; V3 (ignored); U1 (ignored) +Pass ToASCII("xn---3,-3eu051c.xn--9h2d") C1; V6; V3 (ignored); U1 (ignored) +Pass ToASCII("xn----pck1820x.xn--9h2d") V6; V7; V3 (ignored) +Pass ToASCII("xn----pck312bx563c.xn--9h2d") C1; V6; V7; V3 (ignored) +Pass ToASCII("឴.쮇-") V3 (ignored); A4_2 (ignored) +Pass ToASCII("឴.쮇-") V3 (ignored); A4_2 (ignored) +Pass ToASCII(".xn----938f") V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn--z3e.xn----938f") V6; V7; V3 (ignored) +Pass ToASCII("‌U+dcc2。⒈-U+de9b") C1; V7 +Pass ToASCII("‌U+dcc2。1.-U+de9b") C1; V7; V3 (ignored) +Pass ToASCII("xn--wz1d.1.xn----rg03o") V6; V7; V3 (ignored) +Pass ToASCII("xn--0ugy057g.1.xn----rg03o") C1; V7; V3 (ignored) +Pass ToASCII("xn--wz1d.xn----dcp29674o") V6; V7 +Pass ToASCII("xn--0ugy057g.xn----dcp29674o") C1; V7 +Pass ToASCII(".xn--hcb32bni") A4_2 (ignored) +Pass ToASCII("xn--hcb32bni") +Pass ToASCII("ڽ٣֖") +Pass ToASCII("ྔꡋ-.-U+df34") V6; V3 (ignored) +Pass ToASCII("ྔꡋ-.-U+df34") V6; V3 (ignored) +Pass ToASCII("xn----ukg9938i.xn----4u5m") V6; V3 (ignored) +Pass ToASCII("U+dcb3-⋢‌.标-") C1; V7; V3 (ignored) +Pass ToASCII("U+dcb3-⋢‌.标-") C1; V7; V3 (ignored) +Pass ToASCII("U+dcb3-⋢‌.标-") C1; V7; V3 (ignored) +Pass ToASCII("U+dcb3-⋢‌.标-") C1; V7; V3 (ignored) +Pass ToASCII("xn----9mo67451g.xn----qj7b") V7; V3 (ignored) +Pass ToASCII("xn----sgn90kn5663a.xn----qj7b") C1; V7; V3 (ignored) +Pass ToASCII("-U+de74.۠ᢚ-") V6; V7; V3 (ignored) +Pass ToASCII("xn----qi38c.xn----jxc827k") V6; V7; V3 (ignored) +Pass ToASCII("。صىืلا。岓᯲U+df83ᡂ") V7; A4_2 (ignored) +Pass ToASCII(".xn--mgb1a7bt462h.xn--17e10qe61f9r71s") V7; A4_2 (ignored) +Pass ToASCII("ᢌ.-࡚") V3 (ignored) +Pass ToASCII("ᢌ.-࡚") V3 (ignored) +Pass ToASCII("xn--59e.xn----5jd") V3 (ignored) +Pass ToASCII("္-U+dfadU+dfa2.ß") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.ß") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.SS") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.ss") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.Ss") V6; V7 +Pass ToASCII("xn----9tg11172akr8b.ss") V6; V7 +Pass ToASCII("xn----9tg11172akr8b.xn--zca") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.SS") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.ss") V6; V7 +Pass ToASCII("္-U+dfadU+dfa2.Ss") V6; V7 +Pass ToASCII("്-‍‌。U+dfa7₅≠") C1; C2; V6; V7 +Pass ToASCII("്-‍‌。U+dfa7₅≠") C1; C2; V6; V7 +Pass ToASCII("്-‍‌。U+dfa75≠") C1; C2; V6; V7 +Pass ToASCII("്-‍‌。U+dfa75≠") C1; C2; V6; V7 +Pass ToASCII("xn----jmf.xn--5-ufo50192e") V6; V7; V3 (ignored) +Pass ToASCII("xn----jmf215lda.xn--5-ufo50192e") C1; C2; V6; V7 +Pass ToASCII("锣。੍U+de3bU+de86") V6; V7 +Pass ToASCII("xn--gc5a.xn--ybc83044ppga") V6; V7 +Pass ToASCII("xn--8gb2338k.xn--lhb0154f") +Pass ToASCII("ؽU+de3e.ى꤫") +Pass ToASCII("ჁႱ6̘。ßᬃ") +Pass ToASCII("ⴡⴑ6̘。ßᬃ") +Pass ToASCII("ჁႱ6̘。SSᬃ") +Pass ToASCII("ⴡⴑ6̘。ssᬃ") +Pass ToASCII("Ⴡⴑ6̘。Ssᬃ") +Pass ToASCII("xn--6-8cb7433a2ba.xn--ss-2vq") +Pass ToASCII("ⴡⴑ6̘.ssᬃ") +Pass ToASCII("ჁႱ6̘.SSᬃ") +Pass ToASCII("Ⴡⴑ6̘.Ssᬃ") +Pass ToASCII("xn--6-8cb7433a2ba.xn--zca894k") +Pass ToASCII("ⴡⴑ6̘.ßᬃ") +Pass ToASCII("xn--6-8cb306hms1a.xn--ss-2vq") V7 +Pass ToASCII("xn--6-8cb555h2b.xn--ss-2vq") V7 +Pass ToASCII("xn--6-8cb555h2b.xn--zca894k") V7 +Pass ToASCII("U+dc50。≯U+deea") V7 +Pass ToASCII("U+dc50。≯U+deea") V7 +Pass ToASCII("U+dc50。≯U+deea") V7 +Pass ToASCII("U+dc50。≯U+deea") V7 +Pass ToASCII("xn--eo08b.xn--hdh3385g") V7 +Pass ToASCII("U+dd0fU+df34U+dcbd。ᅠ") V6; V7; A4_2 (ignored) +Pass ToASCII("U+dd0fU+df34U+dcbd。ᅠ") V6; V7; A4_2 (ignored) +Pass ToASCII("xn--619ep9154c.") V6; V7; A4_2 (ignored) +Pass ToASCII("xn--619ep9154c.xn--psd") V6; V7 +Pass ToASCII("xn--619ep9154c.xn--cl7c") V6; V7 +Pass ToASCII("U+df54.U+def1₂") V7 +Pass ToASCII("U+df54.U+def12") V7 +Pass ToASCII("xn--vi56e.xn--2-w91i") V7 +Pass ToASCII("⶿.ß‍") C2; V7 +Pass ToASCII("⶿.SS‍") C2; V7 +Pass ToASCII("⶿.ss‍") C2; V7 +Pass ToASCII("⶿.Ss‍") C2; V7 +Pass ToASCII("xn--7pj.ss") V7 +Pass ToASCII("xn--7pj.xn--ss-n1t") C2; V7 +Pass ToASCII("xn--7pj.xn--zca870n") C2; V7 +Pass ToASCII("梉。‌") C1 +Pass ToASCII("xn--7zv.") A4_2 (ignored) +Pass ToASCII("梉.") A4_2 (ignored) +Pass ToASCII("xn--7zv.xn--0ug") C1 +Pass ToASCII("xn--iwb.ss") +Pass ToASCII("ࡓ.ss") +Pass ToASCII("ࡓ.SS") +Pass ToASCII("䃚蟥-。-U+dc98⒈") V7; V3 (ignored) +Pass ToASCII("䃚蟥-。-U+dc981.") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----n50a258u.xn---1-up07j.") V7; V3 (ignored); A4_2 (ignored) +Pass ToASCII("xn----n50a258u.xn----ecp33805f") V7; V3 (ignored) +Pass ToASCII("ᢔ≠U+de42.‍U+dee2") C2; V7 +Pass ToASCII("ᢔ≠U+de42.‍U+dee2") C2; V7 +Pass ToASCII("xn--ebf031cf7196a.xn--587c") V7 +Pass ToASCII("xn--ebf031cf7196a.xn--1ug9540g") C2; V7 +Pass ToASCII("-。⺐") V3 (ignored) +Pass ToASCII("-。⺐") V3 (ignored) +Pass ToASCII("-.xn--6vj") V3 (ignored) +Pass ToASCII("U+dc29U+dcac.ٜ") V6; V7 +Pass ToASCII("U+dc29U+dcac.ٜ") V6; V7 +Pass ToASCII("xn--sn3d59267c.xn--4hb") V6; V7 +Pass ToASCII("U+df7a.U+ddc3‌") C1; V6; V7 +Pass ToASCII("xn--ie8c.xn--2g51a") V6; V7 +Pass ToASCII("xn--ie8c.xn--0ug03366c") C1; V6; V7 +Pass ToASCII("‍≮.U+dfeaU+decf-") C2; V7; V3 (ignored) +Pass ToASCII("‍≮.U+dfeaU+decf-") C2; V7; V3 (ignored) +Pass ToASCII("‍≮.U+dfeaU+decf-") C2; V7; V3 (ignored) +Pass ToASCII("‍≮.U+dfeaU+decf-") C2; V7; V3 (ignored) +Pass ToASCII("xn--gdh.xn----cr99a1w710b") V7; V3 (ignored) +Pass ToASCII("xn--1ug95g.xn----cr99a1w710b") C2; V7; V3 (ignored) +Pass ToASCII("‍‍襔。Ⴜ5ꡮU+df4f") C2; V7 +Pass ToASCII("‍‍襔。ⴜ5ꡮU+df4f") C2; V7 +Pass ToASCII("xn--2u2a.xn--5-uws5848bpf44e") V7 +Pass ToASCII("xn--1uga7691f.xn--5-uws5848bpf44e") C2; V7 +Pass ToASCII("xn--2u2a.xn--5-r1g7167ipfw8d") V7 +Pass ToASCII("xn--1uga7691f.xn--5-r1g7167ipfw8d") C2; V7 +Pass ToASCII("xn--ix9c26l.xn--q0s") +Pass ToASCII("U+dedcU+df3c.婀") +Pass ToASCII("ꦹ‍큷U+dda1。₂") C2; V6; V7 +Pass ToASCII("ꦹ‍큷U+dda1。₂") C2; V6; V7 +Pass ToASCII("xn--0m9as84e2e21c.c") V6; V7 +Pass ToASCII("xn--1ug1435cfkyaoi04d.c") C2; V6; V7 +Pass ToASCII("U+dec0.ډU+df00") +Pass ToASCII("U+dec0.ډU+df00") +Pass ToASCII("xn--pw9c.xn--fjb8658k") +Pass ToASCII("≠膣。ྃ") V6 +Pass ToASCII("≠膣。ྃ") V6 +Pass ToASCII("xn--1chy468a.xn--2ed") V6 +Pass ToASCII("U+def7。‍") C2 +Pass ToASCII("xn--r97c.") A4_2 (ignored) +Pass ToASCII("U+def7.") A4_2 (ignored) +Pass ToASCII("xn--r97c.xn--1ug") C2 +Pass ToASCII("U+dc33U+de2f。⥪") V6 +Pass ToASCII("xn--2g1d14o.xn--jti") V6 +Pass ToASCII("U+dd80䁴U+dde3.ႵU+dfdc‌͈") C1; V6; V7 +Pass ToASCII("U+dd80䁴U+dde3.Ⴕ4‌͈") C1; V6; V7 +Pass ToASCII("U+dd80䁴U+dde3.ⴕ4‌͈") C1; V6; V7 +Pass ToASCII("xn--1mnx647cg3x1b.xn--4-zfb5123a") V6; V7 +Pass ToASCII("xn--1mnx647cg3x1b.xn--4-zfb502tlsl") C1; V6; V7 +Pass ToASCII("U+dd80䁴U+dde3.ⴕU+dfdc‌͈") C1; V6; V7 +Pass ToASCII("xn--1mnx647cg3x1b.xn--4-zfb324h") V6; V7 +Pass ToASCII("xn--1mnx647cg3x1b.xn--4-zfb324h32o") C1; V6; V7 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt b/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt new file mode 100644 index 00000000000..a7fb0cdf031 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt @@ -0,0 +1,400 @@ +Harness status: OK + +Found 386 tests + +386 Pass +Pass Loading data… +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: < :foo.com +> against +Pass Parsing origin: < foo.com > against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <> against +Pass Parsing origin: < > against +Pass Parsing origin: <:foo.com/> against +Pass Parsing origin: <:foo.com\> against +Pass Parsing origin: <:> against +Pass Parsing origin: <:a> against +Pass Parsing origin: <:/> against +Pass Parsing origin: <:\> against +Pass Parsing origin: <:#> against +Pass Parsing origin: <#> against +Pass Parsing origin: <#/> against +Pass Parsing origin: <#\> against +Pass Parsing origin: <#;?> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <:23> against +Pass Parsing origin: against +Pass Parsing origin: <\x> against +Pass Parsing origin: <\\x\hello> against +Pass Parsing origin: <::> against +Pass Parsing origin: <::23> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <[61:24:74]:98> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <#β> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <.> against +Pass Parsing origin: <..> against +Pass Parsing origin: against +Pass Parsing origin: <./test.txt> against +Pass Parsing origin: <../test.txt> against +Pass Parsing origin: <../aaa/test.txt> against +Pass Parsing origin: <../../test.txt> against +Pass Parsing origin: <中/test.txt> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: < http://example.com/ > against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <#> against +Pass Parsing origin: <#x> against +Pass Parsing origin: <#x> against +Pass Parsing origin: <#x:y> against +Pass Parsing origin: <#> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <../i> against +Pass Parsing origin: <../i> against +Pass Parsing origin: <../i> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <#i> against +Pass Parsing origin: <#i> against +Pass Parsing origin: <#i> against +Pass Parsing origin: <#i> against +Pass Parsing origin: <#i> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: bar> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: <#x> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: @[\]^_`{|}~@/> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: @[\]^_`{|}~@host/> against +Pass Parsing origin: @[]^_`{|}~@host/> against +Pass Parsing origin: @[\]^_`{|}~@host/> against +Pass Parsing origin: @[]^_`{|}~@host/> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: @[\]^_`{|}~> against +Pass Parsing origin: @[\]^_`{|}~> against +Pass Parsing origin: ?@[\]^_`{|}~> against +Pass Parsing origin: ?@[\]^_`{|}~> against +Pass Parsing origin: ?@[\]^_`{|}~> against +Pass Parsing origin: ?@[\]^_`{|}~> against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt new file mode 100644 index 00000000000..cd1ce01cb75 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt @@ -0,0 +1,880 @@ +Harness status: OK + +Found 863 tests + +863 Pass +Pass Loading data… +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: < :foo.com +> against +Pass Parsing: < foo.com > against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <> against +Pass Parsing: < > against +Pass Parsing: <:foo.com/> against +Pass Parsing: <:foo.com\> against +Pass Parsing: <:> against +Pass Parsing: <:a> against +Pass Parsing: <:/> against +Pass Parsing: <:\> against +Pass Parsing: <:#> against +Pass Parsing: <#> against +Pass Parsing: <#/> against +Pass Parsing: <#\> against +Pass Parsing: <#;?> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <:23> against +Pass Parsing: against +Pass Parsing: <\x> against +Pass Parsing: <\\x\hello> against +Pass Parsing: <::> against +Pass Parsing: <::23> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <[61:24:74]:98> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <#β> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: < File:c|////foo\bar.html> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <\\server\file> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <.> against +Pass Parsing: <..> against +Pass Parsing: against +Pass Parsing: <./test.txt> against +Pass Parsing: <../test.txt> against +Pass Parsing: <../aaa/test.txt> against +Pass Parsing: <../../test.txt> against +Pass Parsing: <中/test.txt> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: < http://example.com/ > without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <#> against +Pass Parsing: <#x> against +Pass Parsing: <#x> against +Pass Parsing: <#x> against +Pass Parsing: <#x:y> against +Pass Parsing: <#> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: <> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <../i> against +Pass Parsing: <../i> against +Pass Parsing: <../i> against +Pass Parsing: <../i> against +Pass Parsing: <../i> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <#i> against +Pass Parsing: <#i> against +Pass Parsing: <#i> against +Pass Parsing: <#i> against +Pass Parsing: <#i> against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: b> without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: b> without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: bar> without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: <..> against +Pass Parsing: <..> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <..> against +Pass Parsing: <..> against +Pass Parsing: <> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <#x> against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: <\//pig> against +Pass Parsing: <\/localhost//pig> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: <\\\.\Y:> without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: <\\\.\y:> without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: <#x> against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <..//path> against +Pass Parsing: against +Pass Parsing: <> against +Pass Parsing: against +Pass Parsing: <../path> against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: > without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: <10.0.0.7:8080/foo.html> against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: <#link> against +Pass Parsing: without base +Pass Parsing: @[\]^_`{|}~@/> without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: @[\]^_`{|}~@host/> without base +Pass Parsing: @[]^_`{|}~@host/> without base +Pass Parsing: @[\]^_`{|}~@host/> without base +Pass Parsing: @[]^_`{|}~@host/> without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: @[\]^_`{|}~> without base +Pass Parsing: @[\]^_`{|}~> without base +Pass Parsing: ?@[\]^_`{|}~> without base +Pass Parsing: ?@[\]^_`{|}~> without base +Pass Parsing: ?@[\]^_`{|}~> without base +Pass Parsing: ?@[\]^_`{|}~> without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: <#> without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: <> without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: against +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt new file mode 100644 index 00000000000..91df4045059 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt @@ -0,0 +1,401 @@ +Harness status: OK + +Found 387 tests + +387 Pass +Pass Loading data… +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: < :foo.com +> against +Pass Origin parsing: < foo.com > against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <> against +Pass Origin parsing: < > against +Pass Origin parsing: <:foo.com/> against +Pass Origin parsing: <:foo.com\> against +Pass Origin parsing: <:> against +Pass Origin parsing: <:a> against +Pass Origin parsing: <:/> against +Pass Origin parsing: <:\> against +Pass Origin parsing: <:#> against +Pass Origin parsing: <#> against +Pass Origin parsing: <#/> against +Pass Origin parsing: <#\> against +Pass Origin parsing: <#;?> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <:23> against +Pass Origin parsing: against +Pass Origin parsing: <\x> against +Pass Origin parsing: <\\x\hello> against +Pass Origin parsing: <::> against +Pass Origin parsing: <::23> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <[61:24:74]:98> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <#β> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <.> against +Pass Origin parsing: <..> against +Pass Origin parsing: against +Pass Origin parsing: <./test.txt> against +Pass Origin parsing: <../test.txt> against +Pass Origin parsing: <../aaa/test.txt> against +Pass Origin parsing: <../../test.txt> against +Pass Origin parsing: <中/test.txt> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: < http://example.com/ > without base +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: <#> against +Pass Origin parsing: <#x> against +Pass Origin parsing: <#x> against +Pass Origin parsing: <#x> against +Pass Origin parsing: <#x:y> against +Pass Origin parsing: <#> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: <> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <../i> against +Pass Origin parsing: <../i> against +Pass Origin parsing: <../i> against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: <#i> against +Pass Origin parsing: <#i> against +Pass Origin parsing: <#i> against +Pass Origin parsing: <#i> against +Pass Origin parsing: <#i> against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: bar> without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: <#x> against +Pass Origin parsing: against +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: @[\]^_`{|}~@/> without base +Pass Origin parsing: without base +Pass Origin parsing: against +Pass Origin parsing: @[\]^_`{|}~@host/> without base +Pass Origin parsing: @[]^_`{|}~@host/> without base +Pass Origin parsing: @[\]^_`{|}~@host/> without base +Pass Origin parsing: @[]^_`{|}~@host/> without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: @[\]^_`{|}~> without base +Pass Origin parsing: @[\]^_`{|}~> without base +Pass Origin parsing: ?@[\]^_`{|}~> without base +Pass Origin parsing: ?@[\]^_`{|}~> without base +Pass Origin parsing: ?@[\]^_`{|}~> without base +Pass Origin parsing: ?@[\]^_`{|}~> without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-setters-a-area.window.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-setters-a-area.window.txt new file mode 100644 index 0000000000000000000000000000000000000000..333b5c4f1d04018b955fb21363f59519d6e944e0 GIT binary patch literal 43745 zcmeZBEXqqQE>r-=Hhb8FU?C)Ff}w+C`m0YDdyrbH8fQSNGvYqGE#6$ z%*^2eu{nM66H_wt(iKt?OA;S7EQ1KzB-&Xi1gDmifMjeEt@QO%D-v@Ha#Hp3QcLXg z3X1Ye@{{v(6l@jL)fGZAQWa8j3rZ>#ic5+>8ZwI&^72a*5*5l4b23vDijy-^b5r%e z=A{-X9eQ}Bx`JPRo-W*pMX6b-$t9^|xsRY>L_5nM3FItLl85B6 z%=F9>azYHBIYc`~8`UudrFqFErHLh(`FZ3xiLhxzJ1oiBKwDQ2SK`K-K+&`kZ>3JM zx`KaRPNhO|er~D)Qdoe}cVcpKYC%b2Qcfy4A(KR+*~B}VV8SG&=u9GL8nVMON=gc> z^z}hCi385MAT2W|Rb9b3G4D~s%o2s-^30Ot4286!{9JHJpPZjpl9-tXsv$~?Q;YHv zb5r#T5{rw=^NUjS3-XIf6iV_HKt|vQ^nq{!dJq(&R)toEM#d(nB{j$cxRNr+2PB$+ z?11uOkmd@j3gj3d(FtUkf$Rv7|E%=&bMliDb29RaOY~8_5Aq`RBmmL~P6y?gB^e5N z`3fN2IHDS)mim@p@dh|SWBQ;NR}4ZOhFcq`Va18sd;;r1wWGYaSe*;16fMudP0mcrfws#)nJzCsPZuhRBSL6t6_#kp$kZ=LEXhFi4xvaP zpb6O;kas|N31>-$(=HNpB3qaSEp}9xR%!Y93RVh9iACxP!6l_>X$pyHC8QE`cax|KSpft#70mtTala|AMr zW_BTa3N+?mRgzj!qMuQco1U>A~Fj5fYdmYG_vTby57l$@#!R!<#2f{mh+)yQ6k zhHYAYzFu;Eu9b<2F_s2YvARM4s6CuntWcZ^ZWia2=Hw`3rYU5WfT9mHc9N2smROoo zqL7wfq>z_d4lR#xmA~||7K>LwbsM@LdAUkTI17r^6+#k|6cY1NKt9OH%u7{BEJ_8B zA{7)6N}UuNOQcgvN(zd3aTQz8V8!MZRDC2^#sC^J$kQ!JOw!LwEeD5+vw;Gr^Hh|d zQ>;*wS`3Qz%shp>eBJy49JLMshmmIo37+5~GsF=3NU)5LOv@1ZNU%(SOv@1ZkSzn% zPM}&OBR@A)zcRm6KQk{SwL%Y69NOu@ddZ++*4$K3vH-Wj6mnA&^FTwa3NQsY5&?A$ zM|O5;US@@reo<+jKByoo&QHz;4GY7ZL#}(NWGb>lq3zDf{8H3bFuGHn6Z25pz{G|z z^({blKGuOt+N|7Y$Mz$DQexNjsknPRIITlTVPAnE$6{jW_rIz50EF7sF zYB4D$U~vF?#{k*WxWXA~Gf`TxT1n8&1Z^hX1S}4~8OsceoLs!}3W`d~DynMg8k$<# zI=XuL29{RVHnw*54vtaLF|l#+35iwJHMMp1hZ+tw9_|E#@`?biX7Yo>s{=Mk&VTucagOu<(C(y7U9Zm zq-aI95?Wz_s#(wo5Ol5=RIY)9aM?zrE@X?qK?Z6|faVlI=0gWKP-iG`lw_o7#%nDn zEP9);x!O4u7Q?iFbqVf#A~23(OyQV#A~2F(FP(^ z;x*8oXaf-{@fzqrw1Ehfcnv%}pJ)@2lp-4ms^LI0q{x}-@O*UzPZvK&&`PV4qQt!7 zOwi(?#GLqmqWt94;^NG_bR1cXLer2PhNDRa%lNPoIypZV%giQxR0pIShg+#{0kZQE z^9}_$iJ5si;At8=J&=1ql&~yrDnJT!BNXtx3QOL{#&y;~?Q_2!^N^vdz z!gN1vttZ#(R?t}_V*HLHFOcF@a;(GeHAI9WFER!vH|P>$$O2=~6c{)b;fq>|iS`c_ z>_GMe@@yq$;DJWV@muKc4VlnGEXaXQ?BVhe?JY$1UZNGM7f_0;fW(pv-F(oR%FrO6 zVuj?yJcaZ`@Inz#l;MaGDwv4uK+G^lgttM3xtjq97=rVwx`JmwnTbMTN=gxU<$Psc zNn!=4gviS;%1zA4tRgf6iR5`|SVF8fqOGh9tPBiet&G4vFr&5)$TFX3#~T?K7+R$y zSy)*a#afvd7{EM{lUb5ll$Zne40x;f)+I1$~8-)SS#*PzeUAKS*6%MP2L2^%^+k z(Aj@v+exk`?Xkw6J+0!89P23a8aPDJ(@?-sY8spSN6g8&@Q)N%x+aZfFGAO{A4jyYs|3zjAZ%q=mXrUYnw zDiJAJfJQn%?Jl(R0NIyOT#}iS14=GQnJFo$d7$Aekojbe91MU1sS$kObOp{{M(PTV zd6gv@pb9R(Bm=w*J~0ovi8%$d6IY?QB)H)@4sf| zfk%7ko2xOyZ@^thL;ym!r-Dj<&^S)Cm6c(v)nE$00dydl!3P~{1r1B)mzIEsN^n#J zIFbxDFXJ&3zcX>S>#WR7O^r=aW`2snYfBBy6p}!@hw@5N(^HFZH3~2tPd%$B@{$>0 zFX<@cm!%e^<>Z%xs{G7i1z3%(r{IxaTmoM72^!r3H`T9K`UuFmR71(rPu_z-`zXB1|1kA^w7};3x$|ukYHwO@b6hyd;CaeLg@m7XL zq!^AvIkM4c#eZ^sE|LZM`ua(UDWC#HUBM?6a%z}9td|Np`itu?FAPxsZa}kNUiMROBT3l%ozm-V(h_?*lN%Aa2 z(@VUi6?wY48~c zMyMueFk*cvF$N=46EqmH&XgE~5vmCqj970C@Zp&Hp>JPR#hc><|yCv!O@Z7f0d2A&0%=-!~os!j?lLG}iyyvI3Dk8THO zVI^tZUr753pE>v)gQ}m_>j7czB-^YjP3&iJB!j>X?6L}>c%v4zZ z1t)xp*9THyDzZa~O6iEjg5W_KDyG5W>|qjCa8unz{2m0S0|IucD^S&k z)VC797xDX!)(efWBpY(9q{NG~UW9_hi{w~Ii5JOPGlAA##4L>QCmeFDq{NG`>3Hxy z9q4gti3+q`s(~eDspSmhpdipLK`a2kqv_DORd zwgGj*xfIQh*bE`h8HfRAvK@gif*dCxbd%`-gaM>mkI+k&qQuoYug`g;2M1*jP}LtVk8D8E2KUk`FnJE+vh(X%Jl7-F4*E86hc3)h2e30jGa z7;1+FEKO!I$TA$++2FK;)X#y=kdS3AS`MS8(a25)g*5hA4VbIRpV`1^0%;CFnyEkw z6vB>2(o349XtNb0Ta2ckG|N%uEl9Q+MK`*|r~`y{df;V~whHQ??bqay&>ayOBLj)QAXjJ{yQt@q^Qm$iWC|SP?9u z1|sMNkHbg~E}E~)Cnpcm+)*S26OC3r!}2?c#^ZN7xQT{Vp@J-+WC<~jZaZe8r={hj zd%GwxFC{-0w0LAMM+E)^}wNGwqR-%6xj0@&sgIXTaG6v8x@_@JjIS`@hZdrfQ=)D4Yr^%n`M$6`3j zZFI!iKtL0+HK0HQU5#Z0J{JtprNHeAB6J{I03M(rDtPdy#4r%FMiHEm@CG>8L>x+y zjl?_!lwe2`qXpRtP+0})bf)CzCT8Y=mKuZlg^2Lx1P?U8S09%e7#LV8-b^+8wg!rDTKB^fZEsw)ITZ>ofyCkKiS z@O6YG8L62?&}d36N-V~CdKN9LK=DF;zLh@M1E9^8UD6iD<64XwiT z5(!=b?TG~MwFBP)4+;nH*?;8tj^?&vcob*a!(6RMM(P7a8Fg(S)&rpCmbwC(ClYhY z6Dx}$=`Im;KtyH<_#)Vx#NrGb1zZ{)FOY2qvL~?J4q;!CnVXtlT4HNxfLi$Jmn9bI z=j5mB>*;}SpGrzB(u3SSg{w5Aw(-bLht*=RIlH{n66L(aGE^snE(5{kIMNLu*?K)a z@O2>+xd7b^bVs1}ZNT=~=OpH(+oly^hDS_djH;1qOj3-hQL0`{vbq9z2~na#acX)l z=(ZrlQEH&WPSl}=raI`>SrRragF>CQZa@wM0z-1-I7TZkzeEev`^6e8poSw+K{JS4 zh8$ptR-nEPQe980>m*) z617xPHA+=AawYTD3LS;ayyTox@SR?&My?9^r6t9gDX9u6`6VT(DX^SO(j8XFEnK>~ zjg&yMS68rC$V*L4DF$7do|Fo`zAObt)k!E02pLC;)0EW}l&R=6LdGFG4Lp2+R0X0Y zG+dt40iPcVE#2|=+o^8>@y^E;5L9zMITj#0pHL#xw+CH4XAd7a%>|uxU0MK1y7(__ zz*l|{Yb;4lRR%d#nd(j@(pY4tLc5K*`FYu?pfe7Ti>H_vbp^M~oK$d&1blh1Mp9yO zwq`M?6`hK!jY0(zu{cn_G%qtbzbFqi&*|&O=u_ZE3JoQ}nb7dn)6=Jgdr37N+2NoD z7N~GSZ>NA)P=aPDiZa1R#DmsNX6AuMB5>3K6qrPugFtIKaW(I8TaKcOD2wz9NVWw@ z3wA5?^wHXZ1!OKqMa=;COhI-Cc&JQ2FTc1TF*z002(XX>=#IubT~I;*^^?H^H{j6@ zP`KtJj}=1iI)&cmsSv7dU|?i`BSp~3R%DNYs!)91#Nkq6wIN$Ys6tXuR|tWfI}8cr zV#t^>#qB8)OeV#_3`h<}T*R(GEmxCjJi60C(GI5n{-IRkX|8?+yeE9-({ zi$oI$H~`Y?PR-K|4W`^JBp61%vmlnF;k^A0OBg}4VD%(K3duAI9EM>Q+T1RQF2gX6 z0;iFXpdi*_4=lJb#3O<9#b`E)=g60&sA>=v(ZUB}Hf|iVN1_;~? zbVs0<39vM&oR*)jkd%nGH$k*sQY=-Xz)}>wq*!W0fu$&VNwL(90!vZ!l45BB1(u@d zMYa^&=!T^na+}=2nYjfysR}4VP?>q@)(RzwNw_CEaa6#Az(vRbgsoPGg;yd&lDa}b zYEg1(UI}dA%`eo)rxd5Lj?f zXcMxBkk%F_=A;&tXc%fDD#YZ(;*y+Hc;Ss!0g$2<*-GTLyuttjq+SFpaG_)8@IC@^ z=K(HAV#i^CeW^fAZz4w}qL-q8)eaox2C>?ZtwK&UkbWk1Ptmpihm>Y0wins6hycRv zRf1tlSSzxX_)0bs`e$fm1eHuB)}f?!%Sdo7sYW9^89HqZUw2ttSzMButCy3XZl{+~ el$r*bVt|H}fw6&so}rnAor-=Hhb8FU?C)Ffz4NC`m0YDdyq=F#{5di@87qr%!%j zN@iZVLP}yu;-iLT5W&zOA1j66)RGdAj7_4IzJ6*&Vs1fBs$O1diJe|SQGQ8&a(<42 zt%AC`LP$ocLTYY7Nu@$@NfAgxX0bwEeu+Y&LRn%?W{N^_az<)ysvg+9)FOqP)WovX zVug~7RE5wWABEDq~J;`OPMx8<@u|iIMIaDkKn{)6Qd3dF|f?s}~F5Ix9 z)U4Fxl2p=7GDrfM1PWY8Y-XlsmXPcmZB#=FO7oISN)t;m^YchHD#_VETUU?tKu*$0 zR#))P%c)c-&d*I%Kym^otP_)yQwvHGlX6l?cMwTool#O!V5P4Q$}tW&lYUxePO7?s zb7J13hM6S_#pRhL$r%c1Mfthlgq@t9SCW{S2g(qo#i>PkiMgrz1&PJQ<@rS^`UUw# zB?=|^3Lqn}c?D#DF>1E6GBh$aK~2dZ-B^RTycndp!m0w<@uV04GTBOBKPNvqF()Ix zxI`b-VvzH&`y8YZ?EUi0k_?5sdE6%xVG zsiTloS^|#b%shoe1!(BN!YsG6xCCoiUR<1ErLRwvLohNFQY^siDanV~j4fV>vkZGs z6V?k#h*(1#q7`Q>;nbIxpI4lkSDZ>}jsQg+zGM!PFSbg{&sVTgNJ=bHR|qaCO-oZq zOe;w(0$0IlnMK7V3hGwspyD+%Juklqd*uUa9$1y6R+Q*xl;q~<*c8}>q!yz!1ZDoEbYDtT3sB>Z-YB@`kgR!=1k&P}-EXvHs8j8?JMk$n#b>?Dk3q!S86{jW_ zrIz6J6SfeBYDX{bkuApO+T2uv7UR>7)3XeWoLs!}3W`d~DynMg8k$<#I=XuL29{RV zHnw*54vtaLF|l#+35iwJHMMp1hZ+tw9_|FS9799gbS)GLQj3yP^GXy_^OEzy?QC!> z2UNU({FRxPmJg9i1(iei$`+jNQ%yEjwXiG!S?N?+l3J{gm{XLRm{JL|S{H0}Y6`f( z$w^HsQAjM-%`C=hS8*~Z^PuLG3ap`ql)CiwEA&yd;7(c4{0S{6K|ug&<$`h#NC>M< zP%Zl4ES;N*lA~d*V~_!mREH&3!xUjlbJ%k=Ob6MP_ zVCs=vjYlgHZo{J+YgR*c0M@94x*A7LC0095PY=eNitH&`_?DnWqEpec0)N zl!Fo!%=Em}5^_uj1p|8L2i<5;r@_L&!T@JDq8eZYQj6(8u=l}TjJ(p^q|_o%rvg53 z3F>En#yi3JA}upDMIkc}+_wUCGs+TkO0f=BVLBeS-B!^44KbEuiv~=)5MD$czXb;# zbo3T7b_?n)f&B#^6Dh`K0dmg|(=t#S8^4AA-jIG7Vyp$)Kf`KUq7|x%D9I=wu_Qw` zA2fg%8st-~keryOke&!0rvUj5o8vJZjBu<$g}Ivn2pEE6M_s`)pv*)eF(su4Jj`C1 zSCUu(N(_1VMY)MNnN@f@e@N~`*br@HWng7s7;9w&Hr$N5h8r0e7+R$ySy)*a#afvd z7{Dya$t+1NO3Z;<25!fK1|@YAl8cf`(?OkWeM197BNIb2Qo{`9O-R;LS8##$yg|k* zAd+xVYHofR?(hPYZ!nXpfzY8%ED@kz3>r>C9aTzH&{s%F&B@FKB{5Lmz%#0Zne}kn z1r8>9T4;~ebM`dx9KpUlFXbOP#7g;rlh3iff`pJ^Rc!J$aNh!eSo9PNL|4(ud*Zql(F(lGQgwc ziFwf3-W1TRszPx|egS0u8kF%A3KENoK`lb?R7h$GXkZ;QJpye;CFUul78T_eQJPzZ zhzqp`HvGV$guN=w%mcS*2Q+%Y&I3;>BitBmWn~y^HK<(&ZJ2`Ei20=@;3fyf$=S@* z)YufITT=`k(lj(vNCHjTwmXlu&%J`YZ3b1^u zr{IxaTml}}1huljWl?cziH<@^Mt*5}2G(&`EDl79@AUlq^qf>s13Ok-!Ko-eJ2el~ zt01zOt&e1qm6f5Mk)E-hNkuHN)jt;9NZfrOpr^FyI`nJO;mL@Qgw8}z5olq)L=1GoloB!62~$eMfG12T5rdvEr9=#T z!juv*_z6>reE<~CXe3M{QaUGR%n;9rB)Sb$8_Kk(9n<- zQXdN1f=mR>8K>rzWEQ1@mfw;z_DsM|bp`6#huTtEe&K#2CR93?1;fL zdO!GGHNd0vXgLlvL`$`yU~r*|#R5vkfU#BUm~{X)n~M_jQu1>_V|RJ!>fjBKpy6e3 z0x8J{ZJmK^m@LUiEKvY&wNWpD%qJ;8rt86rjj&k{YgJ?>=AhO@*jxb$U~2mx+9U)u zzd-8=LAx((71Rxlu-1K0#VEVU@aq8i53~u%3cP*_QOn@406dyU*d1VHpg})y4B&Pz z#&T!^>OqMC)BsA!&rQtC1C8K{}P_nBIN}o+S*q7 z`lOl+3P04@RBWjT=I-+Hay>*}$jZdn1i4}_FDOx02+qha%}D|6sZdDF$x%owPR`6! zC@4xz%dALEQAo~6EJ{o+Ni8Y{C0tNwVY3d$=pLx$3~O_N7IvvC_!lG=mZmCzB)}`U zK=Z{(;5GHwj0F$ryzYGx7CTd75f#n@LrrRC>a>4QxN&2xhpSgiqV9EQa)%qj(v z?IFQ@&;%fOVhp_58{|0f@;Xwj#2IdwgMZ|N91i0_C6&4YniYvT<%yNWkU&cWEiK3_ z0q_3INi5F5mejCqFt9Jl%uUTNEwME;Kuvu5Wr;=lIr-`OdV1iUMoEc9dXSw)SW_b` zGs1@X@={Bb^AgKY%>-?mAYZqh9(dmcP6JS@60lnPoW#6z+q5D~r^F=2s2aJ(B*my2 zrRv2bt1Ey$EO`0VR;nNp*}?UVe!dsG7y< zBv2Vi*jb5Ipn3%pCeFx{(U&NeG0@QdU<`Ce|!)n*b@#5$7r$@KQ=>n#NOq<2N0bJBc-&P&nw@ zgZ7cx!<&}5phe521(2Y{u_1tVxjIgl>0K2-P+)FfzavFrXZU&q8c^2&Hia zb%hYv>RO1qiy_Tl%8DfhBx4a9wH1gq8`Q->DybkSIJYwxeG6AFw$rMt2fZG&` z-GS2p^ppY%KV{GnFrb5Hh)rV18kNY^XhW_>J90H9;L->#onf9Lw`>m11f9KydIUsf zUb?kHNn#SD%?v&N1*LyTYLF)~B&jO|Ku)-TwY&U6eSC^RTP|RQ6*fabB@(QO3-c=3 zhG8prV1`n$hCmwPMVt}{vnM&RxFiSrsVKg2RB)kJ?FJ7JY>vg3#;91c!Q4Pey$0*+!p0JdD~n4~bM for copy/paste into tests. + // Sample usage: + // test.html?get-keys + match = /(?:^\?|&)get-keys(&get-counts)?(?:&|$)/.exec(location.search); + if (match) { + collectKeys = true; + if (match[1]) { + collectCounts = true; + } + add_completion_callback(() => { + var metas = []; + var template = ''; + if (collectCounts) { + template += ' '; + } + for (var key in keys) { + var meta = template.replace("%s", key); + if (collectCounts) { + meta = meta.replace("%s", keys[key]); + } + metas.push(meta); + } + var pre = document.createElement('pre'); + pre.textContent = metas.join('\n') + '\n'; + document.body.insertBefore(pre, document.body.firstChild); + document.getSelection().selectAllChildren(pre); + }); + } + } + /** + * Check if `key` is in the subset specified in the URL. + * @param {string} key + * @returns {boolean} + */ + function shouldRunSubTest(key) { + if (key && subTestKeyPattern) { + var found = subTestKeyPattern.test(key); + if (exclude) { + return !found; + } + return found; + } + return true; + } + /** + * Only test a subset of tests with `?include=Foo` or `?exclude=Foo` in the URL. + * Can be used together with `` + * Sample usage: + * for (const test of tests) { + * subsetTestByKey("Foo", async_test, test.fn, test.name); + * } + */ + function subsetTestByKey(key, testFunc, ...args) { + if (collectKeys) { + if (collectCounts && key in keys) { + keys[key]++; + } else { + keys[key] = 1; + } + } + if (shouldRunSubTest(key)) { + return testFunc(...args); + } + return null; + } + self.shouldRunSubTest = shouldRunSubTest; + self.subsetTestByKey = subsetTestByKey; +})(); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.html b/Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.html new file mode 100644 index 00000000000..d013a9be52b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.html @@ -0,0 +1,8 @@ + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.js b/Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.js new file mode 100644 index 00000000000..4bfa3118ace --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/IdnaTestV2.window.js @@ -0,0 +1,24 @@ +promise_test(() => fetch("resources/IdnaTestV2.json").then(res => res.json()).then(runTests), "Loading data…"); + +function runTests(idnaTests) { + for (const idnaTest of idnaTests) { + if (typeof idnaTest === "string") { + continue // skip comments + } + if (idnaTest.input === "") { + continue // cannot test empty string input through new URL() + } + + test(() => { + if (idnaTest.output === null) { + assert_throws_js(TypeError, () => new URL(`https://${idnaTest.input}/x`)); + } else { + const url = new URL(`https://${idnaTest.input}/x`); + assert_equals(url.host, idnaTest.output); + assert_equals(url.hostname, idnaTest.output); + assert_equals(url.pathname, "/x"); + assert_equals(url.href, `https://${idnaTest.output}/x`); + } + }, `ToASCII("${idnaTest.input}")${idnaTest.comment ? " " + idnaTest.comment : ""}`); + } +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/a-element-origin.html b/Tests/LibWeb/Text/input/wpt-import/url/a-element-origin.html new file mode 100644 index 00000000000..4bd829214d4 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/a-element-origin.html @@ -0,0 +1,12 @@ + + + + + +
+ + diff --git a/Tests/LibWeb/Text/input/wpt-import/url/resources/IdnaTestV2.json b/Tests/LibWeb/Text/input/wpt-import/url/resources/IdnaTestV2.json new file mode 100644 index 00000000000..f0dcced7102 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/resources/IdnaTestV2.json @@ -0,0 +1,12803 @@ +[ + "THIS IS A GENERATED FILE. PLEASE DO NOT MODIFY DIRECTLY. See ../tools/IdnaTestV2-parser.py instead.", + "--exclude-ipv4-like: True; --exclude-std3: True; --exclude-bidi: True", + { + "input": "fass.de", + "output": "fass.de" + }, + { + "input": "fa\u00df.de", + "output": "xn--fa-hia.de" + }, + { + "input": "Fa\u00df.de", + "output": "xn--fa-hia.de" + }, + { + "input": "xn--fa-hia.de", + "output": "xn--fa-hia.de" + }, + { + "input": "\u00e0.\u05d0\u0308", + "output": "xn--0ca.xn--ssa73l" + }, + { + "input": "a\u0300.\u05d0\u0308", + "output": "xn--0ca.xn--ssa73l" + }, + { + "input": "A\u0300.\u05d0\u0308", + "output": "xn--0ca.xn--ssa73l" + }, + { + "input": "\u00c0.\u05d0\u0308", + "output": "xn--0ca.xn--ssa73l" + }, + { + "input": "xn--0ca.xn--ssa73l", + "output": "xn--0ca.xn--ssa73l" + }, + { + "input": "\u00e0\u0308.\u05d0", + "output": "xn--0ca81i.xn--4db" + }, + { + "input": "a\u0300\u0308.\u05d0", + "output": "xn--0ca81i.xn--4db" + }, + { + "input": "A\u0300\u0308.\u05d0", + "output": "xn--0ca81i.xn--4db" + }, + { + "input": "\u00c0\u0308.\u05d0", + "output": "xn--0ca81i.xn--4db" + }, + { + "input": "xn--0ca81i.xn--4db", + "output": "xn--0ca81i.xn--4db" + }, + { + "comment": "C1", + "input": "a\u200cb", + "output": null + }, + { + "comment": "C1", + "input": "A\u200cB", + "output": null + }, + { + "comment": "C1", + "input": "A\u200cb", + "output": null + }, + { + "input": "ab", + "output": "ab" + }, + { + "comment": "C1", + "input": "xn--ab-j1t", + "output": null + }, + { + "input": "a\u094d\u200cb", + "output": "xn--ab-fsf604u" + }, + { + "input": "A\u094d\u200cB", + "output": "xn--ab-fsf604u" + }, + { + "input": "A\u094d\u200cb", + "output": "xn--ab-fsf604u" + }, + { + "input": "xn--ab-fsf", + "output": "xn--ab-fsf" + }, + { + "input": "a\u094db", + "output": "xn--ab-fsf" + }, + { + "input": "A\u094dB", + "output": "xn--ab-fsf" + }, + { + "input": "A\u094db", + "output": "xn--ab-fsf" + }, + { + "input": "xn--ab-fsf604u", + "output": "xn--ab-fsf604u" + }, + { + "comment": "C2", + "input": "a\u200db", + "output": null + }, + { + "comment": "C2", + "input": "A\u200dB", + "output": null + }, + { + "comment": "C2", + "input": "A\u200db", + "output": null + }, + { + "comment": "C2", + "input": "xn--ab-m1t", + "output": null + }, + { + "input": "a\u094d\u200db", + "output": "xn--ab-fsf014u" + }, + { + "input": "A\u094d\u200dB", + "output": "xn--ab-fsf014u" + }, + { + "input": "A\u094d\u200db", + "output": "xn--ab-fsf014u" + }, + { + "input": "xn--ab-fsf014u", + "output": "xn--ab-fsf014u" + }, + { + "input": "\u00a1", + "output": "xn--7a" + }, + { + "input": "xn--7a", + "output": "xn--7a" + }, + { + "input": "\u19da", + "output": "xn--pkf" + }, + { + "input": "xn--pkf", + "output": "xn--pkf" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "", + "output": "" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "\u3002", + "output": "." + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": ".", + "output": "." + }, + { + "input": "\uab60", + "output": "xn--3y9a" + }, + { + "input": "xn--3y9a", + "output": "xn--3y9a" + }, + { + "comment": "A4_2 (ignored)", + "input": "1234567890\u00e41234567890123456789012345678901234567890123456", + "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe" + }, + { + "comment": "A4_2 (ignored)", + "input": "1234567890a\u03081234567890123456789012345678901234567890123456", + "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe" + }, + { + "comment": "A4_2 (ignored)", + "input": "1234567890A\u03081234567890123456789012345678901234567890123456", + "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe" + }, + { + "comment": "A4_2 (ignored)", + "input": "1234567890\u00c41234567890123456789012345678901234567890123456", + "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe" + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--12345678901234567890123456789012345678901234567890123456-fxe", + "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe" + }, + { + "input": "www.eXample.cOm", + "output": "www.example.com" + }, + { + "input": "B\u00fccher.de", + "output": "xn--bcher-kva.de" + }, + { + "input": "Bu\u0308cher.de", + "output": "xn--bcher-kva.de" + }, + { + "input": "bu\u0308cher.de", + "output": "xn--bcher-kva.de" + }, + { + "input": "b\u00fccher.de", + "output": "xn--bcher-kva.de" + }, + { + "input": "B\u00dcCHER.DE", + "output": "xn--bcher-kva.de" + }, + { + "input": "BU\u0308CHER.DE", + "output": "xn--bcher-kva.de" + }, + { + "input": "xn--bcher-kva.de", + "output": "xn--bcher-kva.de" + }, + { + "input": "\u00d6BB", + "output": "xn--bb-eka" + }, + { + "input": "O\u0308BB", + "output": "xn--bb-eka" + }, + { + "input": "o\u0308bb", + "output": "xn--bb-eka" + }, + { + "input": "\u00f6bb", + "output": "xn--bb-eka" + }, + { + "input": "\u00d6bb", + "output": "xn--bb-eka" + }, + { + "input": "O\u0308bb", + "output": "xn--bb-eka" + }, + { + "input": "xn--bb-eka", + "output": "xn--bb-eka" + }, + { + "input": "FA\u1e9e.de", + "output": "xn--fa-hia.de" + }, + { + "input": "FA\u1e9e.DE", + "output": "xn--fa-hia.de" + }, + { + "input": "\u03b2\u03cc\u03bb\u03bf\u03c2.com", + "output": "xn--nxasmm1c.com" + }, + { + "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c2.com", + "output": "xn--nxasmm1c.com" + }, + { + "input": "\u0392\u039f\u0301\u039b\u039f\u03a3.COM", + "output": "xn--nxasmq6b.com" + }, + { + "input": "\u0392\u038c\u039b\u039f\u03a3.COM", + "output": "xn--nxasmq6b.com" + }, + { + "input": "\u03b2\u03cc\u03bb\u03bf\u03c3.com", + "output": "xn--nxasmq6b.com" + }, + { + "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c3.com", + "output": "xn--nxasmq6b.com" + }, + { + "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c3.com", + "output": "xn--nxasmq6b.com" + }, + { + "input": "\u0392\u03cc\u03bb\u03bf\u03c3.com", + "output": "xn--nxasmq6b.com" + }, + { + "input": "xn--nxasmq6b.com", + "output": "xn--nxasmq6b.com" + }, + { + "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c2.com", + "output": "xn--nxasmm1c.com" + }, + { + "input": "\u0392\u03cc\u03bb\u03bf\u03c2.com", + "output": "xn--nxasmm1c.com" + }, + { + "input": "xn--nxasmm1c.com", + "output": "xn--nxasmm1c.com" + }, + { + "input": "xn--nxasmm1c", + "output": "xn--nxasmm1c" + }, + { + "input": "\u03b2\u03cc\u03bb\u03bf\u03c2", + "output": "xn--nxasmm1c" + }, + { + "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c2", + "output": "xn--nxasmm1c" + }, + { + "input": "\u0392\u039f\u0301\u039b\u039f\u03a3", + "output": "xn--nxasmq6b" + }, + { + "input": "\u0392\u038c\u039b\u039f\u03a3", + "output": "xn--nxasmq6b" + }, + { + "input": "\u03b2\u03cc\u03bb\u03bf\u03c3", + "output": "xn--nxasmq6b" + }, + { + "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c3", + "output": "xn--nxasmq6b" + }, + { + "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c3", + "output": "xn--nxasmq6b" + }, + { + "input": "\u0392\u03cc\u03bb\u03bf\u03c3", + "output": "xn--nxasmq6b" + }, + { + "input": "xn--nxasmq6b", + "output": "xn--nxasmq6b" + }, + { + "input": "\u0392\u03cc\u03bb\u03bf\u03c2", + "output": "xn--nxasmm1c" + }, + { + "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c2", + "output": "xn--nxasmm1c" + }, + { + "input": "www.\u0dc1\u0dca\u200d\u0dbb\u0dd3.com", + "output": "www.xn--10cl1a0b660p.com" + }, + { + "input": "WWW.\u0dc1\u0dca\u200d\u0dbb\u0dd3.COM", + "output": "www.xn--10cl1a0b660p.com" + }, + { + "input": "Www.\u0dc1\u0dca\u200d\u0dbb\u0dd3.com", + "output": "www.xn--10cl1a0b660p.com" + }, + { + "input": "www.xn--10cl1a0b.com", + "output": "www.xn--10cl1a0b.com" + }, + { + "input": "www.\u0dc1\u0dca\u0dbb\u0dd3.com", + "output": "www.xn--10cl1a0b.com" + }, + { + "input": "WWW.\u0dc1\u0dca\u0dbb\u0dd3.COM", + "output": "www.xn--10cl1a0b.com" + }, + { + "input": "Www.\u0dc1\u0dca\u0dbb\u0dd3.com", + "output": "www.xn--10cl1a0b.com" + }, + { + "input": "www.xn--10cl1a0b660p.com", + "output": "www.xn--10cl1a0b660p.com" + }, + { + "input": "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc", + "output": "xn--mgba3gch31f060k" + }, + { + "input": "xn--mgba3gch31f", + "output": "xn--mgba3gch31f" + }, + { + "input": "\u0646\u0627\u0645\u0647\u0627\u06cc", + "output": "xn--mgba3gch31f" + }, + { + "input": "xn--mgba3gch31f060k", + "output": "xn--mgba3gch31f060k" + }, + { + "input": "xn--mgba3gch31f060k.com", + "output": "xn--mgba3gch31f060k.com" + }, + { + "input": "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc.com", + "output": "xn--mgba3gch31f060k.com" + }, + { + "input": "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc.COM", + "output": "xn--mgba3gch31f060k.com" + }, + { + "input": "xn--mgba3gch31f.com", + "output": "xn--mgba3gch31f.com" + }, + { + "input": "\u0646\u0627\u0645\u0647\u0627\u06cc.com", + "output": "xn--mgba3gch31f.com" + }, + { + "input": "\u0646\u0627\u0645\u0647\u0627\u06cc.COM", + "output": "xn--mgba3gch31f.com" + }, + { + "comment": "A4_2 (ignored)", + "input": "a.b\uff0ec\u3002d\uff61", + "output": "a.b.c.d." + }, + { + "comment": "A4_2 (ignored)", + "input": "a.b.c\u3002d\u3002", + "output": "a.b.c.d." + }, + { + "comment": "A4_2 (ignored)", + "input": "A.B.C\u3002D\u3002", + "output": "a.b.c.d." + }, + { + "comment": "A4_2 (ignored)", + "input": "A.b.c\u3002D\u3002", + "output": "a.b.c.d." + }, + { + "comment": "A4_2 (ignored)", + "input": "a.b.c.d.", + "output": "a.b.c.d." + }, + { + "comment": "A4_2 (ignored)", + "input": "A.B\uff0eC\u3002D\uff61", + "output": "a.b.c.d." + }, + { + "comment": "A4_2 (ignored)", + "input": "A.b\uff0ec\u3002D\uff61", + "output": "a.b.c.d." + }, + { + "input": "U\u0308.xn--tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00dc.xn--tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00fc.xn--tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "u\u0308.xn--tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "U\u0308.XN--TDA", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00dc.XN--TDA", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00dc.xn--Tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "U\u0308.xn--Tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "xn--tda.xn--tda", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00fc.\u00fc", + "output": "xn--tda.xn--tda" + }, + { + "input": "u\u0308.u\u0308", + "output": "xn--tda.xn--tda" + }, + { + "input": "U\u0308.U\u0308", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00dc.\u00dc", + "output": "xn--tda.xn--tda" + }, + { + "input": "\u00dc.\u00fc", + "output": "xn--tda.xn--tda" + }, + { + "input": "U\u0308.u\u0308", + "output": "xn--tda.xn--tda" + }, + { + "comment": "V1", + "input": "xn--u-ccb", + "output": null + }, + { + "comment": "V7", + "input": "a\u2488com", + "output": null + }, + { + "input": "a1.com", + "output": "a1.com" + }, + { + "comment": "V7", + "input": "A\u2488COM", + "output": null + }, + { + "comment": "V7", + "input": "A\u2488Com", + "output": null + }, + { + "comment": "V7", + "input": "xn--acom-0w1b", + "output": null + }, + { + "comment": "V7", + "input": "xn--a-ecp.ru", + "output": null + }, + { + "comment": "P4", + "input": "xn--0.pt", + "output": null + }, + { + "comment": "V7", + "input": "xn--a.pt", + "output": null + }, + { + "comment": "P4", + "input": "xn--a-\u00c4.pt", + "output": null + }, + { + "comment": "P4", + "input": "xn--a-A\u0308.pt", + "output": null + }, + { + "comment": "P4", + "input": "xn--a-a\u0308.pt", + "output": null + }, + { + "comment": "P4", + "input": "xn--a-\u00e4.pt", + "output": null + }, + { + "comment": "P4", + "input": "XN--A-\u00c4.PT", + "output": null + }, + { + "comment": "P4", + "input": "XN--A-A\u0308.PT", + "output": null + }, + { + "comment": "P4", + "input": "Xn--A-A\u0308.pt", + "output": null + }, + { + "comment": "P4", + "input": "Xn--A-\u00c4.pt", + "output": null + }, + { + "comment": "V4; V2 (ignored)", + "input": "xn--xn--a--gua.pt", + "output": null + }, + { + "input": "\u65e5\u672c\u8a9e\u3002\uff2a\uff30", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e\u3002JP", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e\u3002jp", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e\u3002Jp", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "xn--wgv71a119e.jp", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e.jp", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e.JP", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e.Jp", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e\u3002\uff4a\uff50", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u65e5\u672c\u8a9e\u3002\uff2a\uff50", + "output": "xn--wgv71a119e.jp" + }, + { + "input": "\u2615", + "output": "xn--53h" + }, + { + "input": "xn--53h", + "output": "xn--53h" + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.a\u00df\u200c\u200db\u200c\u200dc\u00df\u00df\u00df\u00dfd\u03c2\u03c3\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfe\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfx\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfy\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u0302\u00dfz", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.ASS\u200c\u200dB\u200c\u200dCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSS\u0302SSZ", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.ASS\u200c\u200dB\u200c\u200dCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSS\u015cSSZ", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.Ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.Ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "A4_2 (ignored)", + "input": "1.assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "A4_2 (ignored)", + "input": "1.assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "A4_2 (ignored)", + "input": "1.ASSBCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSS\u0302SSZ", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "A4_2 (ignored)", + "input": "1.ASSBCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSS\u015cSSZ", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "A4_2 (ignored)", + "input": "1.Assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "A4_2 (ignored)", + "input": "1.Assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz", + "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa" + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa69989dba9gc", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.A\u00df\u200c\u200db\u200c\u200dc\u00df\u00df\u00df\u00dfd\u03c2\u03c3\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfe\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfx\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfy\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u0302\u00dfz", + "output": null + }, + { + "comment": "C1; C2; A4_2 (ignored)", + "input": "1.xn--abcdexyz-qyacaaabaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaa010ze2isb1140zba8cc", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200cx\u200dn\u200c-\u200d-b\u00df", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200cX\u200dN\u200c-\u200d-BSS", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200cx\u200dn\u200c-\u200d-bss", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200cX\u200dn\u200c-\u200d-Bss", + "output": null + }, + { + "input": "xn--bss", + "output": "xn--bss" + }, + { + "input": "\u5919", + "output": "xn--bss" + }, + { + "comment": "C1; C2", + "input": "xn--xn--bss-7z6ccid", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200cX\u200dn\u200c-\u200d-B\u00df", + "output": null + }, + { + "comment": "C1; C2", + "input": "xn--xn--b-pqa5796ccahd", + "output": null + }, + { + "input": "\u02e3\u034f\u2115\u200b\ufe63\u00ad\uff0d\u180c\u212c\ufe00\u017f\u2064\ud835\udd30\udb40\uddef\ufb04", + "output": "xn--bssffl" + }, + { + "input": "x\u034fN\u200b-\u00ad-\u180cB\ufe00s\u2064s\udb40\uddefffl", + "output": "xn--bssffl" + }, + { + "input": "x\u034fn\u200b-\u00ad-\u180cb\ufe00s\u2064s\udb40\uddefffl", + "output": "xn--bssffl" + }, + { + "input": "X\u034fN\u200b-\u00ad-\u180cB\ufe00S\u2064S\udb40\uddefFFL", + "output": "xn--bssffl" + }, + { + "input": "X\u034fn\u200b-\u00ad-\u180cB\ufe00s\u2064s\udb40\uddefffl", + "output": "xn--bssffl" + }, + { + "input": "xn--bssffl", + "output": "xn--bssffl" + }, + { + "input": "\u5921\u591e\u591c\u5919", + "output": "xn--bssffl" + }, + { + "input": "\u02e3\u034f\u2115\u200b\ufe63\u00ad\uff0d\u180c\u212c\ufe00S\u2064\ud835\udd30\udb40\uddefFFL", + "output": "xn--bssffl" + }, + { + "input": "x\u034fN\u200b-\u00ad-\u180cB\ufe00S\u2064s\udb40\uddefFFL", + "output": "xn--bssffl" + }, + { + "input": "\u02e3\u034f\u2115\u200b\ufe63\u00ad\uff0d\u180c\u212c\ufe00s\u2064\ud835\udd30\udb40\uddefffl", + "output": "xn--bssffl" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.", + "output": "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c", + "output": "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a", + "output": "123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.", + "output": "123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901234.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "input": "\u00e41234567890123456789012345678901234567890123456789012345", + "output": "xn--1234567890123456789012345678901234567890123456789012345-9te" + }, + { + "input": "a\u03081234567890123456789012345678901234567890123456789012345", + "output": "xn--1234567890123456789012345678901234567890123456789012345-9te" + }, + { + "input": "A\u03081234567890123456789012345678901234567890123456789012345", + "output": "xn--1234567890123456789012345678901234567890123456789012345-9te" + }, + { + "input": "\u00c41234567890123456789012345678901234567890123456789012345", + "output": "xn--1234567890123456789012345678901234567890123456789012345-9te" + }, + { + "input": "xn--1234567890123456789012345678901234567890123456789012345-9te", + "output": "xn--1234567890123456789012345678901234567890123456789012345-9te" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00e4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890a\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00e4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890a\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00e4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890a\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901C", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901C", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00e41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890a\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00e41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890a\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789A.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00e41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890a\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890B", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "a.b..-q--a-.e", + "output": "a.b..-q--a-.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "a.b..-q--\u00e4-.e", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "a.b..-q--a\u0308-.e", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "A.B..-Q--A\u0308-.E", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "A.B..-Q--\u00c4-.E", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "A.b..-Q--\u00c4-.E", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "A.b..-Q--A\u0308-.E", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)", + "input": "a.b..xn---q----jra.e", + "output": "a.b..xn---q----jra.e" + }, + { + "comment": "A4_2 (ignored)", + "input": "a..c", + "output": "a..c" + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "a.-b.", + "output": "a.-b." + }, + { + "comment": "V3 (ignored)", + "input": "a.b-.c", + "output": "a.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "a.-.c", + "output": "a.-.c" + }, + { + "comment": "V2 (ignored)", + "input": "a.bc--de.f", + "output": "a.bc--de.f" + }, + { + "comment": "V4; V2 (ignored)", + "input": "xn--xn---epa", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\u00e4.\u00ad.c", + "output": "xn--4ca..c" + }, + { + "comment": "A4_2 (ignored)", + "input": "a\u0308.\u00ad.c", + "output": "xn--4ca..c" + }, + { + "comment": "A4_2 (ignored)", + "input": "A\u0308.\u00ad.C", + "output": "xn--4ca..c" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u00c4.\u00ad.C", + "output": "xn--4ca..c" + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--4ca..c", + "output": "xn--4ca..c" + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "\u00e4.-b.", + "output": "xn--4ca.-b." + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "a\u0308.-b.", + "output": "xn--4ca.-b." + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "A\u0308.-B.", + "output": "xn--4ca.-b." + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "\u00c4.-B.", + "output": "xn--4ca.-b." + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "xn--4ca.-b.", + "output": "xn--4ca.-b." + }, + { + "comment": "V3 (ignored)", + "input": "\u00e4.b-.c", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "a\u0308.b-.c", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "A\u0308.B-.C", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "\u00c4.B-.C", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "\u00c4.b-.C", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "A\u0308.b-.C", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "xn--4ca.b-.c", + "output": "xn--4ca.b-.c" + }, + { + "comment": "V3 (ignored)", + "input": "\u00e4.-.c", + "output": "xn--4ca.-.c" + }, + { + "comment": "V3 (ignored)", + "input": "a\u0308.-.c", + "output": "xn--4ca.-.c" + }, + { + "comment": "V3 (ignored)", + "input": "A\u0308.-.C", + "output": "xn--4ca.-.c" + }, + { + "comment": "V3 (ignored)", + "input": "\u00c4.-.C", + "output": "xn--4ca.-.c" + }, + { + "comment": "V3 (ignored)", + "input": "xn--4ca.-.c", + "output": "xn--4ca.-.c" + }, + { + "comment": "V2 (ignored)", + "input": "\u00e4.bc--de.f", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V2 (ignored)", + "input": "a\u0308.bc--de.f", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V2 (ignored)", + "input": "A\u0308.BC--DE.F", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V2 (ignored)", + "input": "\u00c4.BC--DE.F", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V2 (ignored)", + "input": "\u00c4.bc--De.f", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V2 (ignored)", + "input": "A\u0308.bc--De.f", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V2 (ignored)", + "input": "xn--4ca.bc--de.f", + "output": "xn--4ca.bc--de.f" + }, + { + "comment": "V6", + "input": "a.b.\u0308c.d", + "output": null + }, + { + "comment": "V6", + "input": "A.B.\u0308C.D", + "output": null + }, + { + "comment": "V6", + "input": "A.b.\u0308c.d", + "output": null + }, + { + "comment": "V6", + "input": "a.b.xn--c-bcb.d", + "output": null + }, + { + "input": "A0", + "output": "a0" + }, + { + "input": "0A", + "output": "0a" + }, + { + "input": "\u05d0\u05c7", + "output": "xn--vdbr" + }, + { + "input": "xn--vdbr", + "output": "xn--vdbr" + }, + { + "input": "\u05d09\u05c7", + "output": "xn--9-ihcz" + }, + { + "input": "xn--9-ihcz", + "output": "xn--9-ihcz" + }, + { + "input": "\u05d0\u05ea", + "output": "xn--4db6c" + }, + { + "input": "xn--4db6c", + "output": "xn--4db6c" + }, + { + "input": "\u05d0\u05f3\u05ea", + "output": "xn--4db6c0a" + }, + { + "input": "xn--4db6c0a", + "output": "xn--4db6c0a" + }, + { + "input": "\u05d07\u05ea", + "output": "xn--7-zhc3f" + }, + { + "input": "xn--7-zhc3f", + "output": "xn--7-zhc3f" + }, + { + "input": "\u05d0\u0667\u05ea", + "output": "xn--4db6c6t" + }, + { + "input": "xn--4db6c6t", + "output": "xn--4db6c6t" + }, + { + "input": "\u0bb9\u0bcd\u200d", + "output": "xn--dmc4b194h" + }, + { + "input": "xn--dmc4b", + "output": "xn--dmc4b" + }, + { + "input": "\u0bb9\u0bcd", + "output": "xn--dmc4b" + }, + { + "input": "xn--dmc4b194h", + "output": "xn--dmc4b194h" + }, + { + "comment": "C2", + "input": "\u0bb9\u200d", + "output": null + }, + { + "input": "xn--dmc", + "output": "xn--dmc" + }, + { + "input": "\u0bb9", + "output": "xn--dmc" + }, + { + "comment": "C2", + "input": "xn--dmc225h", + "output": null + }, + { + "comment": "C2", + "input": "\u200d", + "output": null + }, + { + "comment": "C2", + "input": "xn--1ug", + "output": null + }, + { + "input": "\u0bb9\u0bcd\u200c", + "output": "xn--dmc4by94h" + }, + { + "input": "xn--dmc4by94h", + "output": "xn--dmc4by94h" + }, + { + "comment": "C1", + "input": "\u0bb9\u200c", + "output": null + }, + { + "comment": "C1", + "input": "xn--dmc025h", + "output": null + }, + { + "comment": "C1", + "input": "\u200c", + "output": null + }, + { + "comment": "C1", + "input": "xn--0ug", + "output": null + }, + { + "input": "\u0644\u0670\u200c\u06ed\u06ef", + "output": "xn--ghb2gxqia7523a" + }, + { + "input": "xn--ghb2gxqia", + "output": "xn--ghb2gxqia" + }, + { + "input": "\u0644\u0670\u06ed\u06ef", + "output": "xn--ghb2gxqia" + }, + { + "input": "xn--ghb2gxqia7523a", + "output": "xn--ghb2gxqia7523a" + }, + { + "input": "\u0644\u0670\u200c\u06ef", + "output": "xn--ghb2g3qq34f" + }, + { + "input": "xn--ghb2g3q", + "output": "xn--ghb2g3q" + }, + { + "input": "\u0644\u0670\u06ef", + "output": "xn--ghb2g3q" + }, + { + "input": "xn--ghb2g3qq34f", + "output": "xn--ghb2g3qq34f" + }, + { + "input": "\u0644\u200c\u06ed\u06ef", + "output": "xn--ghb25aga828w" + }, + { + "input": "xn--ghb25aga", + "output": "xn--ghb25aga" + }, + { + "input": "\u0644\u06ed\u06ef", + "output": "xn--ghb25aga" + }, + { + "input": "xn--ghb25aga828w", + "output": "xn--ghb25aga828w" + }, + { + "input": "\u0644\u200c\u06ef", + "output": "xn--ghb65a953d" + }, + { + "input": "xn--ghb65a", + "output": "xn--ghb65a" + }, + { + "input": "\u0644\u06ef", + "output": "xn--ghb65a" + }, + { + "input": "xn--ghb65a953d", + "output": "xn--ghb65a953d" + }, + { + "input": "xn--ghb2gxq", + "output": "xn--ghb2gxq" + }, + { + "input": "\u0644\u0670\u06ed", + "output": "xn--ghb2gxq" + }, + { + "comment": "C1", + "input": "\u06ef\u200c\u06ef", + "output": null + }, + { + "input": "xn--cmba", + "output": "xn--cmba" + }, + { + "input": "\u06ef\u06ef", + "output": "xn--cmba" + }, + { + "comment": "C1", + "input": "xn--cmba004q", + "output": null + }, + { + "input": "xn--ghb", + "output": "xn--ghb" + }, + { + "input": "\u0644", + "output": "xn--ghb" + }, + { + "comment": "A4_2 (ignored)", + "input": "a\u3002\u3002b", + "output": "a..b" + }, + { + "comment": "A4_2 (ignored)", + "input": "A\u3002\u3002B", + "output": "a..b" + }, + { + "comment": "A4_2 (ignored)", + "input": "a..b", + "output": "a..b" + }, + { + "comment": "A4_2 (ignored)", + "input": "..xn--skb", + "output": "..xn--skb" + }, + { + "comment": "U1 (ignored)", + "input": "$", + "output": "$" + }, + { + "comment": "U1 (ignored)", + "input": "\u2477.four", + "output": "(4).four" + }, + { + "comment": "U1 (ignored)", + "input": "(4).four", + "output": "(4).four" + }, + { + "comment": "U1 (ignored)", + "input": "\u2477.FOUR", + "output": "(4).four" + }, + { + "comment": "U1 (ignored)", + "input": "\u2477.Four", + "output": "(4).four" + }, + { + "comment": "V7; A3", + "input": "a\ud900z", + "output": null + }, + { + "comment": "V7; A3", + "input": "A\ud900Z", + "output": null + }, + { + "comment": "P4; A4_1 (ignored); A4_2 (ignored)", + "input": "xn--", + "output": null + }, + { + "comment": "P4", + "input": "xn---", + "output": null + }, + { + "comment": "P4", + "input": "xn--ASCII-", + "output": null + }, + { + "input": "ascii", + "output": "ascii" + }, + { + "comment": "P4", + "input": "xn--unicode-.org", + "output": null + }, + { + "input": "unicode.org", + "output": "unicode.org" + }, + { + "input": "\uf951\ud87e\udc68\ud87e\udc74\ud87e\udd1f\ud87e\udd5f\ud87e\uddbf", + "output": "xn--snl253bgitxhzwu2arn60c" + }, + { + "input": "\u964b\u36fc\u5f53\ud850\udfab\u7aee\u45d7", + "output": "xn--snl253bgitxhzwu2arn60c" + }, + { + "input": "xn--snl253bgitxhzwu2arn60c", + "output": "xn--snl253bgitxhzwu2arn60c" + }, + { + "input": "\u96fb\ud844\udf6a\u5f33\u43ab\u7aae\u4d57", + "output": "xn--kbo60w31ob3z6t3av9z5b" + }, + { + "input": "xn--kbo60w31ob3z6t3av9z5b", + "output": "xn--kbo60w31ob3z6t3av9z5b" + }, + { + "input": "xn--A-1ga", + "output": "xn--a-1ga" + }, + { + "input": "a\u00f6", + "output": "xn--a-1ga" + }, + { + "input": "ao\u0308", + "output": "xn--a-1ga" + }, + { + "input": "AO\u0308", + "output": "xn--a-1ga" + }, + { + "input": "A\u00d6", + "output": "xn--a-1ga" + }, + { + "input": "A\u00f6", + "output": "xn--a-1ga" + }, + { + "input": "Ao\u0308", + "output": "xn--a-1ga" + }, + { + "input": "\uff1d\u0338", + "output": "xn--1ch" + }, + { + "input": "\u2260", + "output": "xn--1ch" + }, + { + "input": "=\u0338", + "output": "xn--1ch" + }, + { + "input": "xn--1ch", + "output": "xn--1ch" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b." + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u0308123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_1 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c4123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--1234567890123456789012345678901234567890123456789012345-kue.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901c" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a" + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a.", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789a." + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890A\u03081234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "A4_1 (ignored); A4_2 (ignored)", + "input": "123456789012345678901234567890123456789012345678901234567890123.1234567890\u00c41234567890123456789012345678901234567890123456.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b", + "output": "123456789012345678901234567890123456789012345678901234567890123.xn--12345678901234567890123456789012345678901234567890123456-fxe.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890b" + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u2495\u221d\u065f\uda0e\udd26\uff0e-\udb40\udd2f", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "14.\u221d\u065f\uda0e\udd26.-\udb40\udd2f", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "14.xn--7hb713l3v90n.-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--7hb713lfwbi1311b.-", + "output": null + }, + { + "input": "\ua863.\u07cf", + "output": "xn--8c9a.xn--qsb" + }, + { + "input": "xn--8c9a.xn--qsb", + "output": "xn--8c9a.xn--qsb" + }, + { + "comment": "C2", + "input": "\u200d\u2260\u1899\u226f.\uc1a3-\u1874\u10a0", + "output": null + }, + { + "comment": "C2", + "input": "\u200d=\u0338\u1899>\u0338.\u1109\u1169\u11be-\u1874\u10a0", + "output": null + }, + { + "comment": "C2", + "input": "\u200d=\u0338\u1899>\u0338.\u1109\u1169\u11be-\u1874\u2d00", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u2260\u1899\u226f.\uc1a3-\u1874\u2d00", + "output": null + }, + { + "input": "xn--jbf911clb.xn----p9j493ivi4l", + "output": "xn--jbf911clb.xn----p9j493ivi4l" + }, + { + "input": "\u2260\u1899\u226f.\uc1a3-\u1874\u2d00", + "output": "xn--jbf911clb.xn----p9j493ivi4l" + }, + { + "input": "=\u0338\u1899>\u0338.\u1109\u1169\u11be-\u1874\u2d00", + "output": "xn--jbf911clb.xn----p9j493ivi4l" + }, + { + "input": "=\u0338\u1899>\u0338.\u1109\u1169\u11be-\u1874\u10a0", + "output": "xn--jbf911clb.xn----p9j493ivi4l" + }, + { + "input": "\u2260\u1899\u226f.\uc1a3-\u1874\u10a0", + "output": "xn--jbf911clb.xn----p9j493ivi4l" + }, + { + "comment": "C2", + "input": "xn--jbf929a90b0b.xn----p9j493ivi4l", + "output": null + }, + { + "comment": "V7", + "input": "xn--jbf911clb.xn----6zg521d196p", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--jbf929a90b0b.xn----6zg521d196p", + "output": null + }, + { + "comment": "V7", + "input": "\ud97d\udf9c\uff0e\ud803\udfc7\u0fa2\u077d\u0600", + "output": null + }, + { + "comment": "V7", + "input": "\ud97d\udf9c\uff0e\ud803\udfc7\u0fa1\u0fb7\u077d\u0600", + "output": null + }, + { + "comment": "V7", + "input": "\ud97d\udf9c.\ud803\udfc7\u0fa1\u0fb7\u077d\u0600", + "output": null + }, + { + "comment": "V7", + "input": "xn--gw68a.xn--ifb57ev2psc6027m", + "output": null + }, + { + "comment": "V6", + "input": "\ud84f\udcd4\u0303.\ud805\udcc2", + "output": null + }, + { + "comment": "V6", + "input": "xn--nsa95820a.xn--wz1d", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\ud9d4\udfad.\u10b2\ud804\uddc0", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\ud9d4\udfad.\u2d12\ud804\uddc0", + "output": null + }, + { + "comment": "V7", + "input": "xn--bn95b.xn--9kj2034e", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug15083f.xn--9kj2034e", + "output": null + }, + { + "comment": "V7", + "input": "xn--bn95b.xn--qnd6272k", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug15083f.xn--qnd6272k", + "output": null + }, + { + "comment": "V7", + "input": "\u7e71\ud805\uddbf\u200d.\uff18\ufe12", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--gl0as212a.i.", + "output": "xn--gl0as212a.i." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u7e71\ud805\uddbf.i.", + "output": "xn--gl0as212a.i." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u7e71\ud805\uddbf.I.", + "output": "xn--gl0as212a.i." + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--1ug6928ac48e.i.", + "output": "xn--1ug6928ac48e.i." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u7e71\ud805\uddbf\u200d.i.", + "output": "xn--1ug6928ac48e.i." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u7e71\ud805\uddbf\u200d.I.", + "output": "xn--1ug6928ac48e.i." + }, + { + "comment": "V7", + "input": "xn--gl0as212a.xn--8-o89h", + "output": null + }, + { + "comment": "V7", + "input": "xn--1ug6928ac48e.xn--8-o89h", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\udb40\uddbe\uff0e\ud838\udc08", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\udb40\uddbe.\ud838\udc08", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": ".xn--ph4h", + "output": null + }, + { + "comment": "C2", + "input": "\u00df\u06eb\u3002\u200d", + "output": null + }, + { + "comment": "C2", + "input": "SS\u06eb\u3002\u200d", + "output": null + }, + { + "comment": "C2", + "input": "ss\u06eb\u3002\u200d", + "output": null + }, + { + "comment": "C2", + "input": "Ss\u06eb\u3002\u200d", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--ss-59d.", + "output": "xn--ss-59d." + }, + { + "comment": "A4_2 (ignored)", + "input": "ss\u06eb.", + "output": "xn--ss-59d." + }, + { + "comment": "A4_2 (ignored)", + "input": "SS\u06eb.", + "output": "xn--ss-59d." + }, + { + "comment": "A4_2 (ignored)", + "input": "Ss\u06eb.", + "output": "xn--ss-59d." + }, + { + "comment": "C2", + "input": "xn--ss-59d.xn--1ug", + "output": null + }, + { + "comment": "C2", + "input": "xn--zca012a.xn--1ug", + "output": null + }, + { + "comment": "C1; V7", + "input": "\udb41\udc35\u200c\u2488\uff0e\udb40\udf87", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": "\udb41\udc35\u200c1..\udb40\udf87", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--1-bs31m..xn--tv36e", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": "xn--1-rgn37671n..xn--tv36e", + "output": null + }, + { + "comment": "V7", + "input": "xn--tshz2001k.xn--tv36e", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug88o47900b.xn--tv36e", + "output": null + }, + { + "comment": "V7", + "input": "\udb3c\ude23\u065f\uaab2\u00df\u3002\udaf1\udce7", + "output": null + }, + { + "comment": "V7", + "input": "\udb3c\ude23\u065f\uaab2SS\u3002\udaf1\udce7", + "output": null + }, + { + "comment": "V7", + "input": "\udb3c\ude23\u065f\uaab2ss\u3002\udaf1\udce7", + "output": null + }, + { + "comment": "V7", + "input": "\udb3c\ude23\u065f\uaab2Ss\u3002\udaf1\udce7", + "output": null + }, + { + "comment": "V7", + "input": "xn--ss-3xd2839nncy1m.xn--bb79d", + "output": null + }, + { + "comment": "V7", + "input": "xn--zca92z0t7n5w96j.xn--bb79d", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u0774\u200c\ud83a\udd3f\u3002\ud8b5\ude10\u425c\u200d\ud9be\udd3c", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u0774\u200c\ud83a\udd1d\u3002\ud8b5\ude10\u425c\u200d\ud9be\udd3c", + "output": null + }, + { + "comment": "V7", + "input": "xn--4pb2977v.xn--z0nt555ukbnv", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--4pb607jjt73a.xn--1ug236ke314donv1a", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u3164\u094d\u10a0\u17d0.\u180b", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u1160\u094d\u10a0\u17d0.\u180b", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u1160\u094d\u2d00\u17d0.\u180b", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--n3b445e53p.", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u3164\u094d\u2d00\u17d0.\u180b", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--n3b742bkqf4ty.", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--n3b468aoqa89r.", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--n3b445e53po6d.", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--n3b468azngju2a.", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u2763\u200d\uff0e\u09cd\ud807\udc3d\u0612\ua929", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u2763\u200d.\u09cd\ud807\udc3d\u0612\ua929", + "output": null + }, + { + "comment": "V6", + "input": "xn--pei.xn--0fb32q3w7q2g4d", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--1ugy10a.xn--0fb32q3w7q2g4d", + "output": null + }, + { + "comment": "V6", + "input": "\u0349\u3002\ud85e\udc6b", + "output": null + }, + { + "comment": "V6", + "input": "xn--nua.xn--bc6k", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\ud807\udc3f\udb40\udd66\uff0e\u1160", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\ud807\udc3f\udb40\udd66.\u1160", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--ok3d.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ok3d.xn--psd", + "output": null + }, + { + "comment": "V6", + "input": "\u850f\uff61\ud807\udc3a", + "output": null + }, + { + "comment": "V6", + "input": "\u850f\u3002\ud807\udc3a", + "output": null + }, + { + "comment": "V6", + "input": "xn--uy1a.xn--jk3d", + "output": null + }, + { + "comment": "V7", + "input": "xn--8g1d12120a.xn--5l6h", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud804\udee7\ua9c02\uff61\u39c9\uda09\udd84", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud804\udee7\ua9c02\u3002\u39c9\uda09\udd84", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--2-5z4eu89y.xn--97l02706d", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2938\u03c2\ud8ab\udc40\uff61\uffa0", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2938\u03c2\ud8ab\udc40\u3002\u1160", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2938\u03a3\ud8ab\udc40\u3002\u1160", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2938\u03c3\ud8ab\udc40\u3002\u1160", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--4xa192qmp03d.", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--3xa392qmp03d.", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2938\u03a3\ud8ab\udc40\uff61\uffa0", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2938\u03c3\ud8ab\udc40\uff61\uffa0", + "output": null + }, + { + "comment": "V7", + "input": "xn--4xa192qmp03d.xn--psd", + "output": null + }, + { + "comment": "V7", + "input": "xn--3xa392qmp03d.xn--psd", + "output": null + }, + { + "comment": "V7", + "input": "xn--4xa192qmp03d.xn--cl7c", + "output": null + }, + { + "comment": "V7", + "input": "xn--3xa392qmp03d.xn--cl7c", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u200d\udb7d\udc56\udb40\udc50\uff0e\u05bd\ud826\udfb0\ua85d\ud800\udee1", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u200d\udb7d\udc56\udb40\udc50.\u05bd\ud826\udfb0\ua85d\ud800\udee1", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--b726ey18m.xn--ldb8734fg0qcyzzg", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--1ug66101lt8me.xn--ldb8734fg0qcyzzg", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u3002\udbcc\ude35\u03c2\ud8c2\udc07\u3002\ud802\udf88", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u3002\udbcc\ude35\u03a3\ud8c2\udc07\u3002\ud802\udf88", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u3002\udbcc\ude35\u03c3\ud8c2\udc07\u3002\ud802\udf88", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--4xa68573c7n64d.xn--f29c", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--3xa88573c7n64d.xn--f29c", + "output": null + }, + { + "comment": "V7", + "input": "\u2489\udb40\ude93\u2260\uff61\u10bf\u2b23\u10a8", + "output": null + }, + { + "comment": "V7", + "input": "\u2489\udb40\ude93=\u0338\uff61\u10bf\u2b23\u10a8", + "output": null + }, + { + "comment": "V7", + "input": "2.\udb40\ude93\u2260\u3002\u10bf\u2b23\u10a8", + "output": null + }, + { + "comment": "V7", + "input": "2.\udb40\ude93=\u0338\u3002\u10bf\u2b23\u10a8", + "output": null + }, + { + "comment": "V7", + "input": "2.\udb40\ude93=\u0338\u3002\u2d1f\u2b23\u2d08", + "output": null + }, + { + "comment": "V7", + "input": "2.\udb40\ude93\u2260\u3002\u2d1f\u2b23\u2d08", + "output": null + }, + { + "comment": "V7", + "input": "2.xn--1chz4101l.xn--45iz7d6b", + "output": null + }, + { + "comment": "V7", + "input": "\u2489\udb40\ude93=\u0338\uff61\u2d1f\u2b23\u2d08", + "output": null + }, + { + "comment": "V7", + "input": "\u2489\udb40\ude93\u2260\uff61\u2d1f\u2b23\u2d08", + "output": null + }, + { + "comment": "V7", + "input": "xn--1ch07f91401d.xn--45iz7d6b", + "output": null + }, + { + "comment": "V7", + "input": "2.xn--1chz4101l.xn--gnd9b297j", + "output": null + }, + { + "comment": "V7", + "input": "xn--1ch07f91401d.xn--gnd9b297j", + "output": null + }, + { + "input": "\ud83a\udd37.\ud802\udf90\ud83a\udc81\ud803\ude60\u0624", + "output": "xn--ve6h.xn--jgb1694kz0b2176a" + }, + { + "input": "\ud83a\udd37.\ud802\udf90\ud83a\udc81\ud803\ude60\u0648\u0654", + "output": "xn--ve6h.xn--jgb1694kz0b2176a" + }, + { + "input": "\ud83a\udd15.\ud802\udf90\ud83a\udc81\ud803\ude60\u0648\u0654", + "output": "xn--ve6h.xn--jgb1694kz0b2176a" + }, + { + "input": "\ud83a\udd15.\ud802\udf90\ud83a\udc81\ud803\ude60\u0624", + "output": "xn--ve6h.xn--jgb1694kz0b2176a" + }, + { + "input": "xn--ve6h.xn--jgb1694kz0b2176a", + "output": "xn--ve6h.xn--jgb1694kz0b2176a" + }, + { + "comment": "V7; V3 (ignored); U1 (ignored)", + "input": "-\udb40\ude56\ua867\uff0e\udb40\ude82\ud8dc\udd83\ud83c\udd09", + "output": null + }, + { + "comment": "V7; V3 (ignored); U1 (ignored)", + "input": "-\udb40\ude56\ua867.\udb40\ude82\ud8dc\udd838,", + "output": null + }, + { + "comment": "V7; V3 (ignored); U1 (ignored)", + "input": "xn----hg4ei0361g.xn--8,-k362evu488a", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----hg4ei0361g.xn--207ht163h7m94c", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u200c\uff61\u0354", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u200c\u3002\u0354", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": ".xn--yua", + "output": null + }, + { + "comment": "C1; V6", + "input": "xn--0ug.xn--yua", + "output": null + }, + { + "input": "\ud83a\udd25\udb40\udd6e\uff0e\u1844\u10ae", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd25\udb40\udd6e.\u1844\u10ae", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd25\udb40\udd6e.\u1844\u2d0e", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd03\udb40\udd6e.\u1844\u10ae", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd03\udb40\udd6e.\u1844\u2d0e", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "xn--de6h.xn--37e857h", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd25.\u1844\u2d0e", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd03.\u1844\u10ae", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd03.\u1844\u2d0e", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd25\udb40\udd6e\uff0e\u1844\u2d0e", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd03\udb40\udd6e\uff0e\u1844\u10ae", + "output": "xn--de6h.xn--37e857h" + }, + { + "input": "\ud83a\udd03\udb40\udd6e\uff0e\u1844\u2d0e", + "output": "xn--de6h.xn--37e857h" + }, + { + "comment": "V7", + "input": "xn--de6h.xn--mnd799a", + "output": null + }, + { + "input": "\ud83a\udd25.\u1844\u10ae", + "output": "xn--de6h.xn--37e857h" + }, + { + "comment": "V6; V7", + "input": "\u0fa4\ud986\udd2f\uff0e\ud835\udfed\u10bb", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0fa4\ud986\udd2f.1\u10bb", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0fa4\ud986\udd2f.1\u2d1b", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--0fd40533g.xn--1-tws", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0fa4\ud986\udd2f\uff0e\ud835\udfed\u2d1b", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--0fd40533g.xn--1-q1g", + "output": null + }, + { + "comment": "V7", + "input": "\u03c2\ud9d5\udf0c\uff18.\ud83a\udf64", + "output": null + }, + { + "comment": "V7", + "input": "\u03c2\ud9d5\udf0c8.\ud83a\udf64", + "output": null + }, + { + "comment": "V7", + "input": "\u03a3\ud9d5\udf0c8.\ud83a\udf64", + "output": null + }, + { + "comment": "V7", + "input": "\u03c3\ud9d5\udf0c8.\ud83a\udf64", + "output": null + }, + { + "comment": "V7", + "input": "xn--8-zmb14974n.xn--su6h", + "output": null + }, + { + "comment": "V7", + "input": "xn--8-xmb44974n.xn--su6h", + "output": null + }, + { + "comment": "V7", + "input": "\u03a3\ud9d5\udf0c\uff18.\ud83a\udf64", + "output": null + }, + { + "comment": "V7", + "input": "\u03c3\ud9d5\udf0c\uff18.\ud83a\udf64", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c\uae03.\u69b6-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c\u1100\u1173\u11b2.\u69b6-", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "xn--ej0b.xn----d87b", + "output": "xn--ej0b.xn----d87b" + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn--0ug3307c.xn----d87b", + "output": null + }, + { + "comment": "V6", + "input": "\ub253\u6cd3\ud833\udd7d.\u09cd\u200d", + "output": null + }, + { + "comment": "V6", + "input": "\u1102\u1170\u11be\u6cd3\ud833\udd7d.\u09cd\u200d", + "output": null + }, + { + "comment": "V6", + "input": "xn--lwwp69lqs7m.xn--b7b", + "output": null + }, + { + "comment": "V6", + "input": "xn--lwwp69lqs7m.xn--b7b605i", + "output": null + }, + { + "comment": "V6", + "input": "\u1b44\uff0e\u1baa-\u226e\u2260", + "output": null + }, + { + "comment": "V6", + "input": "\u1b44\uff0e\u1baa-<\u0338=\u0338", + "output": null + }, + { + "comment": "V6", + "input": "\u1b44.\u1baa-\u226e\u2260", + "output": null + }, + { + "comment": "V6", + "input": "\u1b44.\u1baa-<\u0338=\u0338", + "output": null + }, + { + "comment": "V6", + "input": "xn--1uf.xn----nmlz65aub", + "output": null + }, + { + "comment": "V6", + "input": "\u1bf3\u10b1\u115f\uff0e\ud804\udd34\u2132", + "output": null + }, + { + "comment": "V6", + "input": "\u1bf3\u10b1\u115f.\ud804\udd34\u2132", + "output": null + }, + { + "comment": "V6", + "input": "\u1bf3\u2d11\u115f.\ud804\udd34\u214e", + "output": null + }, + { + "comment": "V6", + "input": "\u1bf3\u10b1\u115f.\ud804\udd34\u214e", + "output": null + }, + { + "comment": "V6", + "input": "xn--1zf224e.xn--73g3065g", + "output": null + }, + { + "comment": "V6", + "input": "\u1bf3\u2d11\u115f\uff0e\ud804\udd34\u214e", + "output": null + }, + { + "comment": "V6", + "input": "\u1bf3\u10b1\u115f\uff0e\ud804\udd34\u214e", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--pnd26a55x.xn--73g3065g", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--osd925cvyn.xn--73g3065g", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--pnd26a55x.xn--f3g7465g", + "output": null + }, + { + "comment": "V7", + "input": "\u10a9\u7315\udba5\udeeb\u226e\uff0e\ufe12", + "output": null + }, + { + "comment": "V7", + "input": "\u10a9\u7315\udba5\udeeb<\u0338\uff0e\ufe12", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u10a9\u7315\udba5\udeeb\u226e.\u3002", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u10a9\u7315\udba5\udeeb<\u0338.\u3002", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2d09\u7315\udba5\udeeb<\u0338.\u3002", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2d09\u7315\udba5\udeeb\u226e.\u3002", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--gdh892bbz0d5438s..", + "output": null + }, + { + "comment": "V7", + "input": "\u2d09\u7315\udba5\udeeb<\u0338\uff0e\ufe12", + "output": null + }, + { + "comment": "V7", + "input": "\u2d09\u7315\udba5\udeeb\u226e\uff0e\ufe12", + "output": null + }, + { + "comment": "V7", + "input": "xn--gdh892bbz0d5438s.xn--y86c", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--hnd212gz32d54x5r..", + "output": null + }, + { + "comment": "V7", + "input": "xn--hnd212gz32d54x5r.xn--y86c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u00c5\ub444-\uff0e\u200c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "A\u030a\u1103\u116d\u11b7-\uff0e\u200c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u00c5\ub444-.\u200c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "A\u030a\u1103\u116d\u11b7-.\u200c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "a\u030a\u1103\u116d\u11b7-.\u200c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u00e5\ub444-.\u200c", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "xn----1fa1788k.", + "output": "xn----1fa1788k." + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn----1fa1788k.xn--0ug", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "a\u030a\u1103\u116d\u11b7-\uff0e\u200c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u00e5\ub444-\uff0e\u200c", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "\ub8f1\u200d\ud880\udf68\u200c\u3002\ud836\ude16\ufe12", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "\u1105\u116e\u11b0\u200d\ud880\udf68\u200c\u3002\ud836\ude16\ufe12", + "output": null + }, + { + "comment": "C1; C2; V6; A4_2 (ignored)", + "input": "\ub8f1\u200d\ud880\udf68\u200c\u3002\ud836\ude16\u3002", + "output": null + }, + { + "comment": "C1; C2; V6; A4_2 (ignored)", + "input": "\u1105\u116e\u11b0\u200d\ud880\udf68\u200c\u3002\ud836\ude16\u3002", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--ct2b0738h.xn--772h.", + "output": null + }, + { + "comment": "C1; C2; V6; A4_2 (ignored)", + "input": "xn--0ugb3358ili2v.xn--772h.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ct2b0738h.xn--y86cl899a", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "xn--0ugb3358ili2v.xn--y86cl899a", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "\ud83c\udd04\uff0e\u1cdc\u2488\u00df", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "3,.\u1cdc1.\u00df", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "3,.\u1cdc1.SS", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "3,.\u1cdc1.ss", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "3,.\u1cdc1.Ss", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "3,.xn--1-43l.ss", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "3,.xn--1-43l.xn--zca", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "\ud83c\udd04\uff0e\u1cdc\u2488SS", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "\ud83c\udd04\uff0e\u1cdc\u2488ss", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "\ud83c\udd04\uff0e\u1cdc\u2488Ss", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "3,.xn--ss-k1r094b", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "3,.xn--zca344lmif", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--x07h.xn--ss-k1r094b", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--x07h.xn--zca344lmif", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u1dfd\u103a\u094d\uff0e\u2260\u200d\u31db", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u103a\u094d\u1dfd\uff0e\u2260\u200d\u31db", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u103a\u094d\u1dfd\uff0e=\u0338\u200d\u31db", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u103a\u094d\u1dfd.\u2260\u200d\u31db", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u103a\u094d\u1dfd.=\u0338\u200d\u31db", + "output": null + }, + { + "comment": "V6", + "input": "xn--n3b956a9zm.xn--1ch912d", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--n3b956a9zm.xn--1ug63gz5w", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\u1bf3.-\u900b\ud98e\uddad\udb25\ude6e", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--1zf.xn----483d46987byr50b", + "output": null + }, + { + "input": "xn--9ob.xn--4xa", + "output": "xn--9ob.xn--4xa" + }, + { + "input": "\u0756.\u03c3", + "output": "xn--9ob.xn--4xa" + }, + { + "input": "\u0756.\u03a3", + "output": "xn--9ob.xn--4xa" + }, + { + "comment": "V7", + "input": "xn--9ob.xn--4xa380e", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--9ob.xn--4xa380ebol", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--9ob.xn--3xa580ebol", + "output": null + }, + { + "comment": "V7", + "input": "xn--9ob.xn--4xa574u", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--9ob.xn--4xa795lq2l", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--9ob.xn--3xa995lq2l", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u1846\u10a3\uff61\udb3a\udca7\u0315\u200d\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u1846\u10a3\u3002\udb3a\udca7\u0315\u200d\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u1846\u2d03\u3002\udb3a\udca7\u0315\u200d\u200d", + "output": null + }, + { + "comment": "V7", + "input": "xn--57e237h.xn--5sa98523p", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--57e237h.xn--5sa649la993427a", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u1846\u2d03\uff61\udb3a\udca7\u0315\u200d\u200d", + "output": null + }, + { + "comment": "V7", + "input": "xn--bnd320b.xn--5sa98523p", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--bnd320b.xn--5sa649la993427a", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud838\udc28\uff61\u1b44\uda45\udee8\ud838\udf87", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud838\udc28\u3002\u1b44\uda45\udee8\ud838\udf87", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--mi4h.xn--1uf6843smg20c", + "output": null + }, + { + "comment": "V7", + "input": "\u189b\udb60\udd5f\u00df.\u1327", + "output": null + }, + { + "comment": "V7", + "input": "\u189b\udb60\udd5fSS.\u1327", + "output": null + }, + { + "comment": "V7", + "input": "\u189b\udb60\udd5fss.\u1327", + "output": null + }, + { + "comment": "V7", + "input": "\u189b\udb60\udd5fSs.\u1327", + "output": null + }, + { + "comment": "V7", + "input": "xn--ss-7dp66033t.xn--p5d", + "output": null + }, + { + "comment": "V7", + "input": "xn--zca562jc642x.xn--p5d", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u2b92\u200c.\ud909\ude97\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--b9i.xn--5p9y", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ugx66b.xn--0ugz2871c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u226f\ud805\udf2b\udb42\udf47.\u1734\ud909\udfa4\ud804\udf6c\u18a7", + "output": null + }, + { + "comment": "V6; V7", + "input": ">\u0338\ud805\udf2b\udb42\udf47.\u1734\ud909\udfa4\ud804\udf6c\u18a7", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--hdhx157g68o0g.xn--c0e65eu616c34o7a", + "output": null + }, + { + "input": "\u00df\uff61\ud800\udef3\u10ac\u0fb8", + "output": "xn--zca.xn--lgd921mvv0m" + }, + { + "input": "\u00df\u3002\ud800\udef3\u10ac\u0fb8", + "output": "xn--zca.xn--lgd921mvv0m" + }, + { + "input": "\u00df\u3002\ud800\udef3\u2d0c\u0fb8", + "output": "xn--zca.xn--lgd921mvv0m" + }, + { + "input": "SS\u3002\ud800\udef3\u10ac\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "ss\u3002\ud800\udef3\u2d0c\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "Ss\u3002\ud800\udef3\u10ac\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "ss.xn--lgd921mvv0m", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "ss.\ud800\udef3\u2d0c\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "SS.\ud800\udef3\u10ac\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "Ss.\ud800\udef3\u10ac\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "xn--zca.xn--lgd921mvv0m", + "output": "xn--zca.xn--lgd921mvv0m" + }, + { + "input": "\u00df.\ud800\udef3\u2d0c\u0fb8", + "output": "xn--zca.xn--lgd921mvv0m" + }, + { + "input": "\u00df\uff61\ud800\udef3\u2d0c\u0fb8", + "output": "xn--zca.xn--lgd921mvv0m" + }, + { + "input": "SS\uff61\ud800\udef3\u10ac\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "ss\uff61\ud800\udef3\u2d0c\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "input": "Ss\uff61\ud800\udef3\u10ac\u0fb8", + "output": "ss.xn--lgd921mvv0m" + }, + { + "comment": "V7", + "input": "ss.xn--lgd10cu829c", + "output": null + }, + { + "comment": "V7", + "input": "xn--zca.xn--lgd10cu829c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1a5a\ud82e\udd9d\u0c4d\u3002\ud829\udf6c\ud835\udff5", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1a5a\ud82e\udd9d\u0c4d\u3002\ud829\udf6c9", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--lqc703ebm93a.xn--9-000p", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\u1856\uff61\u031f\ud91d\udee8\u0b82-", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\u1856\u3002\u031f\ud91d\udee8\u0b82-", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--m8e.xn----mdb555dkk71m", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0596\u10ab\uff0e\ud835\udff3\u226f\ufe12\ufe0a", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0596\u10ab\uff0e\ud835\udff3>\u0338\ufe12\ufe0a", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u0596\u10ab.7\u226f\u3002\ufe0a", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u0596\u10ab.7>\u0338\u3002\ufe0a", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u0596\u2d0b.7>\u0338\u3002\ufe0a", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u0596\u2d0b.7\u226f\u3002\ufe0a", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--hcb613r.xn--7-pgo.", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0596\u2d0b\uff0e\ud835\udff3>\u0338\ufe12\ufe0a", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0596\u2d0b\uff0e\ud835\udff3\u226f\ufe12\ufe0a", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--hcb613r.xn--7-pgoy530h", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": "xn--hcb887c.xn--7-pgo.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--hcb887c.xn--7-pgoy530h", + "output": null + }, + { + "comment": "V7; U1 (ignored)", + "input": "\ud83c\udd07\u4f10\ufe12.\ud831\ude5a\ua8c4", + "output": null + }, + { + "comment": "V7; U1 (ignored); A4_2 (ignored)", + "input": "6,\u4f10\u3002.\ud831\ude5a\ua8c4", + "output": null + }, + { + "comment": "V7; U1 (ignored); A4_2 (ignored)", + "input": "xn--6,-7i3c..xn--0f9ao925c", + "output": null + }, + { + "comment": "V7; U1 (ignored)", + "input": "xn--6,-7i3cj157d.xn--0f9ao925c", + "output": null + }, + { + "comment": "V7", + "input": "xn--woqs083bel0g.xn--0f9ao925c", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\udb40\udda0\uff0e\ud99d\udc34\udaf1\udfc8", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\udb40\udda0.\ud99d\udc34\udaf1\udfc8", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--rx21bhv12i", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "-.\u1886\udb47\udca3-", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "-.xn----pbkx6497q", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0\uff0e-\ud835\udffb\u00df", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0.-5\u00df", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0.-5SS", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0.-5ss", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--t960e.-5ss", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--t960e.xn---5-hia", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0\uff0e-\ud835\udffbSS", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0\uff0e-\ud835\udffbss", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0\uff0e-\ud835\udffbSs", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udafd\udcb0.-5Ss", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\ud802\ude3f.\ud83e\udd12\u10c5\uda06\udfb6", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\ud802\ude3f.\ud83e\udd12\u2d25\uda06\udfb6", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--0s9c.xn--tljz038l0gz4b", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug9533g.xn--tljz038l0gz4b", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--0s9c.xn--9nd3211w0gz4b", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug9533g.xn--9nd3211w0gz4b", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud894\udec5\u3002\u00df\ud873\udd69\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud894\udec5\u3002SS\ud873\udd69\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud894\udec5\u3002ss\ud873\udd69\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud894\udec5\u3002Ss\ud873\udd69\u200d", + "output": null + }, + { + "comment": "V7", + "input": "xn--ey1p.xn--ss-eq36b", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--ey1p.xn--ss-n1tx0508a", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--ey1p.xn--zca870nz438b", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\uff61\ud805\uddbf\u1abb\u03c2\u2260", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\uff61\ud805\uddbf\u1abb\u03c2=\u0338", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\u3002\ud805\uddbf\u1abb\u03c2\u2260", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\u3002\ud805\uddbf\u1abb\u03c2=\u0338", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\u3002\ud805\uddbf\u1abb\u03a3=\u0338", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\u3002\ud805\uddbf\u1abb\u03a3\u2260", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\u3002\ud805\uddbf\u1abb\u03c3\u2260", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\u3002\ud805\uddbf\u1abb\u03c3=\u0338", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--zb9h5968x.xn--4xa378i1mfjw7y", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--0ug3766p5nm1b.xn--4xa378i1mfjw7y", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--0ug3766p5nm1b.xn--3xa578i1mfjw7y", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\uff61\ud805\uddbf\u1abb\u03a3=\u0338", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\uff61\ud805\uddbf\u1abb\u03a3\u2260", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\uff61\ud805\uddbf\u1abb\u03c3\u2260", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\udb40\udd6f\ud9df\udf6d\u200c\ud83d\udf2d\uff61\ud805\uddbf\u1abb\u03c3=\u0338", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u248b\uff61\u2488\u200d\uda8f\udd22", + "output": null + }, + { + "comment": "C2; V7; A4_2 (ignored)", + "input": "4.\u30021.\u200d\uda8f\udd22", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "4..1.xn--sf51d", + "output": null + }, + { + "comment": "C2; V7; A4_2 (ignored)", + "input": "4..1.xn--1ug64613i", + "output": null + }, + { + "comment": "V7", + "input": "xn--wsh.xn--tsh07994h", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--wsh.xn--1ug58o74922a", + "output": null + }, + { + "comment": "V7", + "input": "\u10b3\ud805\udf2b\u200d\uda1e\udf53\uff0e\u06a7\ud807\udc36", + "output": null + }, + { + "comment": "V7", + "input": "\u10b3\ud805\udf2b\u200d\uda1e\udf53.\u06a7\ud807\udc36", + "output": null + }, + { + "comment": "V7", + "input": "\u2d13\ud805\udf2b\u200d\uda1e\udf53.\u06a7\ud807\udc36", + "output": null + }, + { + "comment": "V7", + "input": "xn--blj6306ey091d.xn--9jb4223l", + "output": null + }, + { + "comment": "V7", + "input": "xn--1ugy52cym7p7xu5e.xn--9jb4223l", + "output": null + }, + { + "comment": "V7", + "input": "\u2d13\ud805\udf2b\u200d\uda1e\udf53\uff0e\u06a7\ud807\udc36", + "output": null + }, + { + "comment": "V7", + "input": "xn--rnd8945ky009c.xn--9jb4223l", + "output": null + }, + { + "comment": "V7", + "input": "xn--rnd479ep20q7x12e.xn--9jb4223l", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "\ud802\ude3f.\ud83c\udd06\u2014", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "\ud802\ude3f.5,\u2014", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "xn--0s9c.xn--5,-81t", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--0s9c.xn--8ug8324p", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\uda10\udeb1\ud8c6\uddae\u06f8\u3002\udb43\udfad-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--lmb18944c0g2z.xn----2k81m", + "output": null + }, + { + "comment": "V7", + "input": "\ud83d\udf85\udb43\udce1\udb30\udf59.\ud989\uddb7", + "output": null + }, + { + "comment": "V7", + "input": "xn--ie9hi1349bqdlb.xn--oj69a", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\u20e7\ud97e\udc4e-\uda6e\udcdd.4\u10a4\u200c", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\u20e7\ud97e\udc4e-\uda6e\udcdd.4\u2d04\u200c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn----9snu5320fi76w.xn--4-ivs", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn----9snu5320fi76w.xn--4-sgn589c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn----9snu5320fi76w.xn--4-f0g", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn----9snu5320fi76w.xn--4-f0g649i", + "output": null + }, + { + "input": "\u16ad\uff61\ud834\udf20\u00df\ud81a\udef1", + "output": "xn--hwe.xn--zca4946pblnc" + }, + { + "input": "\u16ad\u3002\ud834\udf20\u00df\ud81a\udef1", + "output": "xn--hwe.xn--zca4946pblnc" + }, + { + "input": "\u16ad\u3002\ud834\udf20SS\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad\u3002\ud834\udf20ss\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad\u3002\ud834\udf20Ss\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "xn--hwe.xn--ss-ci1ub261a", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad.\ud834\udf20ss\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad.\ud834\udf20SS\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad.\ud834\udf20Ss\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "xn--hwe.xn--zca4946pblnc", + "output": "xn--hwe.xn--zca4946pblnc" + }, + { + "input": "\u16ad.\ud834\udf20\u00df\ud81a\udef1", + "output": "xn--hwe.xn--zca4946pblnc" + }, + { + "input": "\u16ad\uff61\ud834\udf20SS\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad\uff61\ud834\udf20ss\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "input": "\u16ad\uff61\ud834\udf20Ss\ud81a\udef1", + "output": "xn--hwe.xn--ss-ci1ub261a" + }, + { + "comment": "V6", + "input": "\ud805\udc44\u226f\uff61\ud805\udf24", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udc44>\u0338\uff61\ud805\udf24", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udc44\u226f\u3002\ud805\udf24", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udc44>\u0338\u3002\ud805\udf24", + "output": null + }, + { + "comment": "V6", + "input": "xn--hdh5636g.xn--ci2d", + "output": null + }, + { + "comment": "C2", + "input": "\u10ab\u226e\ud887\udc86\u3002\u200d\u07a7\ud800\udee3", + "output": null + }, + { + "comment": "C2", + "input": "\u10ab<\u0338\ud887\udc86\u3002\u200d\u07a7\ud800\udee3", + "output": null + }, + { + "comment": "C2", + "input": "\u2d0b<\u0338\ud887\udc86\u3002\u200d\u07a7\ud800\udee3", + "output": null + }, + { + "comment": "C2", + "input": "\u2d0b\u226e\ud887\udc86\u3002\u200d\u07a7\ud800\udee3", + "output": null + }, + { + "comment": "V6", + "input": "xn--gdhz03bxt42d.xn--lrb6479j", + "output": null + }, + { + "comment": "C2", + "input": "xn--gdhz03bxt42d.xn--lrb506jqr4n", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--jnd802gsm17c.xn--lrb6479j", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--jnd802gsm17c.xn--lrb506jqr4n", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u17d2.\ud9db\udf52\u226f", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u17d2.\ud9db\udf52>\u0338", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--u4e.xn--hdhx0084f", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3a\u00c9\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3aE\u0301\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734.\ud802\ude3a\u00c9\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734.\ud802\ude3aE\u0301\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734.\ud802\ude3ae\u0301\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734.\ud802\ude3a\u00e9\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--c0e34564d.xn--9ca207st53lg3f", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3ae\u0301\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3a\u00e9\u2b13\ud804\udd34", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--09e4694e..xn--ye6h", + "output": "xn--09e4694e..xn--ye6h" + }, + { + "comment": "V6", + "input": "\u10c3\uff0e\u0653\u18a4", + "output": null + }, + { + "comment": "V6", + "input": "\u10c3.\u0653\u18a4", + "output": null + }, + { + "comment": "V6", + "input": "\u2d23.\u0653\u18a4", + "output": null + }, + { + "comment": "V6", + "input": "xn--rlj.xn--vhb294g", + "output": null + }, + { + "comment": "V6", + "input": "\u2d23\uff0e\u0653\u18a4", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--7nd.xn--vhb294g", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813\uff0e\uc2c9\ud9d0\uddbb\u10c4\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813\uff0e\u1109\u1174\u11b0\ud9d0\uddbb\u10c4\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813.\uc2c9\ud9d0\uddbb\u10c4\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813.\u1109\u1174\u11b0\ud9d0\uddbb\u10c4\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813.\u1109\u1174\u11b0\ud9d0\uddbb\u2d24\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813.\uc2c9\ud9d0\uddbb\u2d24\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "xn--oub.xn--sljz109bpe25dviva", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813\uff0e\u1109\u1174\u11b0\ud9d0\uddbb\u2d24\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "\udb40\udd08\u0813\uff0e\uc2c9\ud9d0\uddbb\u2d24\ud9ca\udc50", + "output": null + }, + { + "comment": "V7", + "input": "xn--oub.xn--8nd9522gpe69cviva", + "output": null + }, + { + "comment": "V6", + "input": "\uaa2c\ud807\udcab\u226e\uff0e\u2902", + "output": null + }, + { + "comment": "V6", + "input": "\uaa2c\ud807\udcab<\u0338\uff0e\u2902", + "output": null + }, + { + "comment": "V6", + "input": "\uaa2c\ud807\udcab\u226e.\u2902", + "output": null + }, + { + "comment": "V6", + "input": "\uaa2c\ud807\udcab<\u0338.\u2902", + "output": null + }, + { + "comment": "V6", + "input": "xn--gdh1854cn19c.xn--kqi", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\ud804\udc45\u3002-", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--210d.-", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\ua866\u1851\u200d\u2488\u3002\ud800\udee3-", + "output": null + }, + { + "comment": "C2; V3 (ignored); A4_2 (ignored)", + "input": "\ua866\u1851\u200d1.\u3002\ud800\udee3-", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "xn--1-o7j0610f..xn----381i", + "output": "xn--1-o7j0610f..xn----381i" + }, + { + "comment": "C2; V3 (ignored); A4_2 (ignored)", + "input": "xn--1-o7j663bdl7m..xn----381i", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--h8e863drj7h.xn----381i", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn--h8e470bl0d838o.xn----381i", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u2488\u4c39\u200d-\u3002\uc6c8", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u2488\u4c39\u200d-\u3002\u110b\u116e\u11bf", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "1.\u4c39\u200d-\u3002\uc6c8", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "1.\u4c39\u200d-\u3002\u110b\u116e\u11bf", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "1.xn----zw5a.xn--kp5b", + "output": "1.xn----zw5a.xn--kp5b" + }, + { + "comment": "C2; V3 (ignored)", + "input": "1.xn----tgnz80r.xn--kp5b", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----dcp160o.xn--kp5b", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----tgnx5rjr6c.xn--kp5b", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u3066\u3002\u200c\udb43\udcfd\u07f3", + "output": null + }, + { + "comment": "V7", + "input": "xn--m9j.xn--rtb10784p", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--m9j.xn--rtb154j9l73w", + "output": null + }, + { + "comment": "V6", + "input": "\u03c2\uff61\ua9c0\u06e7", + "output": null + }, + { + "comment": "V6", + "input": "\u03c2\u3002\ua9c0\u06e7", + "output": null + }, + { + "comment": "V6", + "input": "\u03a3\u3002\ua9c0\u06e7", + "output": null + }, + { + "comment": "V6", + "input": "\u03c3\u3002\ua9c0\u06e7", + "output": null + }, + { + "comment": "V6", + "input": "xn--4xa.xn--3lb1944f", + "output": null + }, + { + "comment": "V6", + "input": "xn--3xa.xn--3lb1944f", + "output": null + }, + { + "comment": "V6", + "input": "\u03a3\uff61\ua9c0\u06e7", + "output": null + }, + { + "comment": "V6", + "input": "\u03c3\uff61\ua9c0\u06e7", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0bcd\udb56\udec5\ud9f0\ude51.\u10a2\u10b5", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0bcd\udb56\udec5\ud9f0\ude51.\u2d02\u2d15", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0bcd\udb56\udec5\ud9f0\ude51.\u10a2\u2d15", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--xmc83135idcxza.xn--tkjwb", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--xmc83135idcxza.xn--9md086l", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--xmc83135idcxza.xn--9md2b", + "output": null + }, + { + "comment": "C2; V6; V7; U1 (ignored)", + "input": "\u1c32\ud83c\udd08\u2f9b\u05a6\uff0e\u200d\uda7e\udd64\u07fd", + "output": null + }, + { + "comment": "C2; V6; V7; U1 (ignored)", + "input": "\u1c327,\u8d70\u05a6.\u200d\uda7e\udd64\u07fd", + "output": null + }, + { + "comment": "V6; V7; U1 (ignored)", + "input": "xn--7,-bid991urn3k.xn--1tb13454l", + "output": null + }, + { + "comment": "C2; V6; V7; U1 (ignored)", + "input": "xn--7,-bid991urn3k.xn--1tb334j1197q", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--xcb756i493fwi5o.xn--1tb13454l", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--xcb756i493fwi5o.xn--1tb334j1197q", + "output": null + }, + { + "comment": "V7", + "input": "\u1897\uff61\u04c0\ud934\udd3b", + "output": null + }, + { + "comment": "V7", + "input": "\u1897\u3002\u04c0\ud934\udd3b", + "output": null + }, + { + "comment": "V7", + "input": "\u1897\u3002\u04cf\ud934\udd3b", + "output": null + }, + { + "comment": "V7", + "input": "xn--hbf.xn--s5a83117e", + "output": null + }, + { + "comment": "V7", + "input": "\u1897\uff61\u04cf\ud934\udd3b", + "output": null + }, + { + "comment": "V7", + "input": "xn--hbf.xn--d5a86117e", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "-\ud800\udef7\ud81b\udf91\u3002\udb40\uddac", + "output": "xn----991iq40y." + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "xn----991iq40y.", + "output": "xn----991iq40y." + }, + { + "comment": "V6", + "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\uff61\ud835\udfea\u10bc", + "output": null + }, + { + "comment": "V6", + "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\u30028\u10bc", + "output": null + }, + { + "comment": "V6", + "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\u30028\u2d1c", + "output": null + }, + { + "comment": "V6", + "input": "xn--7m3d291b.xn--8-vws", + "output": null + }, + { + "comment": "V6", + "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\uff61\ud835\udfea\u2d1c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--7m3d291b.xn--8-s1g", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1bab\uff61\ud83c\udc89\udb40\udc70", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1bab\u3002\ud83c\udc89\udb40\udc70", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--zxf.xn--fx7ho0250c", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb71\udeb6\udba0\uded6\uda1a\ude70-\u3002\u200c", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "xn----7i12hu122k9ire.", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn----7i12hu122k9ire.xn--0ug", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ufe12\uff0e\ufe2f\ud805\udc42", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ufe12\uff0e\ud805\udc42\ufe2f", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u3002.\ud805\udc42\ufe2f", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "..xn--s96cu30b", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--y86c.xn--s96cu30b", + "output": null + }, + { + "comment": "C2; V6", + "input": "\ua92c\u3002\u200d", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--zi9a.", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--zi9a.xn--1ug", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udb58\ude04\u3002-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--xm38e.-", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee\uff0e\uda98\ude2e\u0f18\u00df\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee\uff0e\uda98\ude2e\u0f18\u00df>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee.\uda98\ude2e\u0f18\u00df\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee.\uda98\ude2e\u0f18\u00df>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee.\uda98\ude2e\u0f18SS>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee.\uda98\ude2e\u0f18SS\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee.\uda98\ude2e\u0f18ss\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee.\uda98\ude2e\u0f18ss>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee.\uda98\ude2e\u0f18Ss>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee.\uda98\ude2e\u0f18Ss\u226f", + "output": null + }, + { + "comment": "V7", + "input": "xn--pgh4639f.xn--ss-ifj426nle504a", + "output": null + }, + { + "comment": "V7", + "input": "xn--pgh4639f.xn--zca593eo6oc013y", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee\uff0e\uda98\ude2e\u0f18SS>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee\uff0e\uda98\ude2e\u0f18SS\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee\uff0e\uda98\ude2e\u0f18ss\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee\uff0e\uda98\ude2e\u0f18ss>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u227c\u0338\ud800\udeee\uff0e\uda98\ude2e\u0f18Ss>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\u22e0\ud800\udeee\uff0e\uda98\ude2e\u0f18Ss\u226f", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0330\uff0e\udb81\udf31\u8680", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0330.\udb81\udf31\u8680", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--xta.xn--e91aw9417e", + "output": null + }, + { + "comment": "C2; V6; U1 (ignored)", + "input": "\ud83e\udc9f\ud83c\udd08\u200d\ua84e\uff61\u0f84", + "output": null + }, + { + "comment": "C2; V6; U1 (ignored)", + "input": "\ud83e\udc9f7,\u200d\ua84e\u3002\u0f84", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "xn--7,-gh9hg322i.xn--3ed", + "output": null + }, + { + "comment": "C2; V6; U1 (ignored)", + "input": "xn--7,-n1t0654eqo3o.xn--3ed", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--nc9aq743ds0e.xn--3ed", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--1ug4874cfd0kbmg.xn--3ed", + "output": null + }, + { + "comment": "V6", + "input": "\ua854\u3002\u1039\u1887", + "output": null + }, + { + "comment": "V6", + "input": "xn--tc9a.xn--9jd663b", + "output": null + }, + { + "comment": "V6", + "input": "\u20eb\u226e.\ud836\ude16", + "output": null + }, + { + "comment": "V6", + "input": "\u20eb<\u0338.\ud836\ude16", + "output": null + }, + { + "comment": "V6", + "input": "xn--e1g71d.xn--772h", + "output": null + }, + { + "comment": "C1", + "input": "\u200c.\u226f", + "output": null + }, + { + "comment": "C1", + "input": "\u200c.>\u0338", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--hdh", + "output": ".xn--hdh" + }, + { + "comment": "C1", + "input": "xn--0ug.xn--hdh", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\ud880\udd67\ud94e\ude60-\uff0e\uabed-\u609c", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\ud880\udd67\ud94e\ude60-.\uabed-\u609c", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----7m53aj640l.xn----8f4br83t", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u1849\ud899\udce7\u2b1e\u189c.-\u200d\ud83a\udcd1\u202e", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--87e0ol04cdl39e.xn----qinu247r", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn--87e0ol04cdl39e.xn----ugn5e3763s", + "output": null + }, + { + "input": "\ud83a\udd53\uff0e\u0718", + "output": "xn--of6h.xn--inb" + }, + { + "input": "\ud83a\udd53.\u0718", + "output": "xn--of6h.xn--inb" + }, + { + "input": "xn--of6h.xn--inb", + "output": "xn--of6h.xn--inb" + }, + { + "comment": "V3 (ignored)", + "input": "\udb40\udd3d-\uff0e-\u0dca", + "output": "-.xn----ptf" + }, + { + "comment": "V3 (ignored)", + "input": "\udb40\udd3d-.-\u0dca", + "output": "-.xn----ptf" + }, + { + "comment": "V3 (ignored)", + "input": "-.xn----ptf", + "output": "-.xn----ptf" + }, + { + "input": "\u10ba\ud800\udef8\udb40\udd04\u3002\ud835\udfdd\ud7f6\u103a", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "input": "\u10ba\ud800\udef8\udb40\udd04\u30025\ud7f6\u103a", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "input": "\u2d1a\ud800\udef8\udb40\udd04\u30025\ud7f6\u103a", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "input": "xn--ilj2659d.xn--5-dug9054m", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "input": "\u2d1a\ud800\udef8.5\ud7f6\u103a", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "input": "\u10ba\ud800\udef8.5\ud7f6\u103a", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "input": "\u2d1a\ud800\udef8\udb40\udd04\u3002\ud835\udfdd\ud7f6\u103a", + "output": "xn--ilj2659d.xn--5-dug9054m" + }, + { + "comment": "V7", + "input": "xn--ynd2415j.xn--5-dug9054m", + "output": null + }, + { + "comment": "C2; V6; U1 (ignored)", + "input": "\u200d-\u1839\ufe6a.\u1de1\u1922", + "output": null + }, + { + "comment": "C2; V6; U1 (ignored)", + "input": "\u200d-\u1839%.\u1de1\u1922", + "output": null + }, + { + "comment": "V6; V3 (ignored); U1 (ignored)", + "input": "xn---%-u4o.xn--gff52t", + "output": null + }, + { + "comment": "C2; V6; U1 (ignored)", + "input": "xn---%-u4oy48b.xn--gff52t", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----c6jx047j.xn--gff52t", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn----c6j614b1z4v.xn--gff52t", + "output": null + }, + { + "input": "\u2260.\u183f", + "output": "xn--1ch.xn--y7e" + }, + { + "input": "=\u0338.\u183f", + "output": "xn--1ch.xn--y7e" + }, + { + "input": "xn--1ch.xn--y7e", + "output": "xn--1ch.xn--y7e" + }, + { + "input": "\u0723\u05a3\uff61\u332a", + "output": "xn--ucb18e.xn--eck4c5a" + }, + { + "input": "\u0723\u05a3\u3002\u30cf\u30a4\u30c4", + "output": "xn--ucb18e.xn--eck4c5a" + }, + { + "input": "xn--ucb18e.xn--eck4c5a", + "output": "xn--ucb18e.xn--eck4c5a" + }, + { + "input": "\u0723\u05a3.\u30cf\u30a4\u30c4", + "output": "xn--ucb18e.xn--eck4c5a" + }, + { + "comment": "V7", + "input": "\ud84e\ude6b\uff0e\ud9f1\udc72", + "output": null + }, + { + "comment": "V7", + "input": "\ud84e\ude6b.\ud9f1\udc72", + "output": null + }, + { + "comment": "V7", + "input": "xn--td3j.xn--4628b", + "output": null + }, + { + "input": "xn--skb", + "output": "xn--skb" + }, + { + "input": "\u06b9", + "output": "xn--skb" + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u0c4d\ud836\ude3e\u05a9\ud835\udfed\u3002-\ud805\udf28", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u0c4d\ud836\ude3e\u05a91\u3002-\ud805\udf28", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--1-rfc312cdp45c.xn----nq0j", + "output": null + }, + { + "comment": "V7", + "input": "\uda4f\udfc8\u3002\ub64f", + "output": null + }, + { + "comment": "V7", + "input": "\uda4f\udfc8\u3002\u1104\u116b\u11ae", + "output": null + }, + { + "comment": "V7", + "input": "xn--ph26c.xn--281b", + "output": null + }, + { + "comment": "V7", + "input": "\ud916\ude1a\udb40\udd0c\udb07\udf40\u1840.\u08b6", + "output": null + }, + { + "comment": "V7", + "input": "xn--z7e98100evc01b.xn--czb", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\uff61\ud8d4\udc5b", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u3002\ud8d4\udc5b", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--6x4u", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug.xn--6x4u", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ufff9\u200c\uff61\u66f3\u2f91\ud800\udef0\u226f", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ufff9\u200c\uff61\u66f3\u2f91\ud800\udef0>\u0338", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ufff9\u200c\u3002\u66f3\u897e\ud800\udef0\u226f", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ufff9\u200c\u3002\u66f3\u897e\ud800\udef0>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "xn--vn7c.xn--hdh501y8wvfs5h", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug2139f.xn--hdh501y8wvfs5h", + "output": null + }, + { + "comment": "V7", + "input": "\u226f\u2488\u3002\u00df", + "output": null + }, + { + "comment": "V7", + "input": ">\u0338\u2488\u3002\u00df", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\u226f1.\u3002\u00df", + "output": "xn--1-ogo..xn--zca" + }, + { + "comment": "A4_2 (ignored)", + "input": ">\u03381.\u3002\u00df", + "output": "xn--1-ogo..xn--zca" + }, + { + "comment": "A4_2 (ignored)", + "input": ">\u03381.\u3002SS", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u226f1.\u3002SS", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u226f1.\u3002ss", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": ">\u03381.\u3002ss", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": ">\u03381.\u3002Ss", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u226f1.\u3002Ss", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--1-ogo..ss", + "output": "xn--1-ogo..ss" + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--1-ogo..xn--zca", + "output": "xn--1-ogo..xn--zca" + }, + { + "comment": "V7", + "input": ">\u0338\u2488\u3002SS", + "output": null + }, + { + "comment": "V7", + "input": "\u226f\u2488\u3002SS", + "output": null + }, + { + "comment": "V7", + "input": "\u226f\u2488\u3002ss", + "output": null + }, + { + "comment": "V7", + "input": ">\u0338\u2488\u3002ss", + "output": null + }, + { + "comment": "V7", + "input": ">\u0338\u2488\u3002Ss", + "output": null + }, + { + "comment": "V7", + "input": "\u226f\u2488\u3002Ss", + "output": null + }, + { + "comment": "V7", + "input": "xn--hdh84f.ss", + "output": null + }, + { + "comment": "V7", + "input": "xn--hdh84f.xn--zca", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\uff61\u2260", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\uff61=\u0338", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u3002\u2260", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u3002=\u0338", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--1ch", + "output": ".xn--1ch" + }, + { + "comment": "C1", + "input": "xn--0ug.xn--1ch", + "output": null + }, + { + "comment": "C1; V6", + "input": "\ud805\uddbf\ud836\ude14.\u185f\ud805\uddbf\u1b42\u200c", + "output": null + }, + { + "comment": "V6", + "input": "xn--461dw464a.xn--v8e29loy65a", + "output": null + }, + { + "comment": "C1; V6", + "input": "xn--461dw464a.xn--v8e29ldzfo952a", + "output": null + }, + { + "comment": "C2; V6; V7; V3 (ignored)", + "input": "\uda12\udcf3\u200d\uda05\udf71.\ud81a\udf34\u2183\u2260-", + "output": null + }, + { + "comment": "C2; V6; V7; V3 (ignored)", + "input": "\uda12\udcf3\u200d\uda05\udf71.\ud81a\udf34\u2183=\u0338-", + "output": null + }, + { + "comment": "C2; V6; V7; V3 (ignored)", + "input": "\uda12\udcf3\u200d\uda05\udf71.\ud81a\udf34\u2184=\u0338-", + "output": null + }, + { + "comment": "C2; V6; V7; V3 (ignored)", + "input": "\uda12\udcf3\u200d\uda05\udf71.\ud81a\udf34\u2184\u2260-", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--6j00chy9a.xn----81n51bt713h", + "output": null + }, + { + "comment": "C2; V6; V7; V3 (ignored)", + "input": "xn--1ug15151gkb5a.xn----81n51bt713h", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--6j00chy9a.xn----61n81bt713h", + "output": null + }, + { + "comment": "C2; V6; V7; V3 (ignored)", + "input": "xn--1ug15151gkb5a.xn----61n81bt713h", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u252e\udb40\uddd0\uff0e\u0c00\u0c4d\u1734\u200d", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u252e\udb40\uddd0.\u0c00\u0c4d\u1734\u200d", + "output": null + }, + { + "comment": "V6", + "input": "xn--kxh.xn--eoc8m432a", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--1ug04r.xn--eoc8m432a40i", + "output": null + }, + { + "comment": "V7; U1 (ignored)", + "input": "\udaa5\udeaa\uff61\ud83c\udd02", + "output": null + }, + { + "comment": "V7; U1 (ignored)", + "input": "\udaa5\udeaa\u30021,", + "output": null + }, + { + "comment": "V7; U1 (ignored)", + "input": "xn--n433d.1,", + "output": null + }, + { + "comment": "V7", + "input": "xn--n433d.xn--v07h", + "output": null + }, + { + "comment": "V6", + "input": "\ud804\udf68\u520d.\ud83d\udee6", + "output": null + }, + { + "comment": "V6", + "input": "xn--rbry728b.xn--y88h", + "output": null + }, + { + "comment": "V6; V7", + "input": "\udb40\udf0f3\uff61\u1bf1\ud835\udfd2", + "output": null + }, + { + "comment": "V6; V7", + "input": "\udb40\udf0f3\u3002\u1bf14", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--3-ib31m.xn--4-pql", + "output": null + }, + { + "comment": "V7", + "input": "\ua87d\u226f\uff0e\udaaf\udc80\uda0b\udcc4", + "output": null + }, + { + "comment": "V7", + "input": "\ua87d>\u0338\uff0e\udaaf\udc80\uda0b\udcc4", + "output": null + }, + { + "comment": "V7", + "input": "\ua87d\u226f.\udaaf\udc80\uda0b\udcc4", + "output": null + }, + { + "comment": "V7", + "input": "\ua87d>\u0338.\udaaf\udc80\uda0b\udcc4", + "output": null + }, + { + "comment": "V7", + "input": "xn--hdh8193c.xn--5z40cp629b", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb\uff0e\u200d\u492b\u2260\u10be", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb\uff0e\u200d\u492b=\u0338\u10be", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb.\u200d\u492b\u2260\u10be", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb.\u200d\u492b=\u0338\u10be", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb.\u200d\u492b=\u0338\u2d1e", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb.\u200d\u492b\u2260\u2d1e", + "output": null + }, + { + "comment": "V7", + "input": "xn--1t56e.xn--1ch153bqvw", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1t56e.xn--1ug73gzzpwi3a", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb\uff0e\u200d\u492b=\u0338\u2d1e", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udb43\udcdb\uff0e\u200d\u492b\u2260\u2d1e", + "output": null + }, + { + "comment": "V7", + "input": "xn--1t56e.xn--2nd141ghl2a", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1t56e.xn--2nd159e9vb743e", + "output": null + }, + { + "comment": "V6", + "input": "3.1.xn--110d.j", + "output": null + }, + { + "comment": "V7", + "input": "xn--tshd3512p.j", + "output": null + }, + { + "comment": "V6", + "input": "\u034a\uff0e\ud802\ude0e", + "output": null + }, + { + "comment": "V6", + "input": "\u034a.\ud802\ude0e", + "output": null + }, + { + "comment": "V6", + "input": "xn--oua.xn--mr9c", + "output": null + }, + { + "comment": "V6", + "input": "\ud6c9\u226e\uff61\u0e34", + "output": null + }, + { + "comment": "V6", + "input": "\u1112\u116e\u11ac<\u0338\uff61\u0e34", + "output": null + }, + { + "comment": "V6", + "input": "\ud6c9\u226e\u3002\u0e34", + "output": null + }, + { + "comment": "V6", + "input": "\u1112\u116e\u11ac<\u0338\u3002\u0e34", + "output": null + }, + { + "comment": "V6", + "input": "xn--gdh2512e.xn--i4c", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "\ua846\u3002\u2183\u0fb5\ub1ae-", + "output": "xn--fc9a.xn----qmg097k469k" + }, + { + "comment": "V3 (ignored)", + "input": "\ua846\u3002\u2183\u0fb5\u1102\u116a\u11c1-", + "output": "xn--fc9a.xn----qmg097k469k" + }, + { + "comment": "V3 (ignored)", + "input": "\ua846\u3002\u2184\u0fb5\u1102\u116a\u11c1-", + "output": "xn--fc9a.xn----qmg097k469k" + }, + { + "comment": "V3 (ignored)", + "input": "\ua846\u3002\u2184\u0fb5\ub1ae-", + "output": "xn--fc9a.xn----qmg097k469k" + }, + { + "comment": "V3 (ignored)", + "input": "xn--fc9a.xn----qmg097k469k", + "output": "xn--fc9a.xn----qmg097k469k" + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--fc9a.xn----qmg787k869k", + "output": null + }, + { + "comment": "V7", + "input": "\u226e\ud834\udd76\uff0e\ud987\udc81\uaaec\u2e48\udb82\udd6d", + "output": null + }, + { + "comment": "V7", + "input": "<\u0338\ud834\udd76\uff0e\ud987\udc81\uaaec\u2e48\udb82\udd6d", + "output": null + }, + { + "comment": "V7", + "input": "\u226e\ud834\udd76.\ud987\udc81\uaaec\u2e48\udb82\udd6d", + "output": null + }, + { + "comment": "V7", + "input": "<\u0338\ud834\udd76.\ud987\udc81\uaaec\u2e48\udb82\udd6d", + "output": null + }, + { + "comment": "V7", + "input": "xn--gdh.xn--4tjx101bsg00ds9pyc", + "output": null + }, + { + "comment": "V7", + "input": "xn--gdh0880o.xn--4tjx101bsg00ds9pyc", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud805\udc42\uff61\u200d\udb55\udf80\ud83d\udf95\uda54\udc54", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud805\udc42\u3002\u200d\udb55\udf80\ud83d\udf95\uda54\udc54", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--8v1d.xn--ye9h41035a2qqs", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--8v1d.xn--1ug1386plvx1cd8vya", + "output": null + }, + { + "input": "\u00df\u09c1\u1ded\u3002\u06208\u2085", + "output": "xn--zca266bwrr.xn--85-psd" + }, + { + "input": "\u00df\u09c1\u1ded\u3002\u062085", + "output": "xn--zca266bwrr.xn--85-psd" + }, + { + "input": "SS\u09c1\u1ded\u3002\u062085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "ss\u09c1\u1ded\u3002\u062085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "Ss\u09c1\u1ded\u3002\u062085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "xn--ss-e2f077r.xn--85-psd", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "ss\u09c1\u1ded.\u062085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "SS\u09c1\u1ded.\u062085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "Ss\u09c1\u1ded.\u062085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "xn--zca266bwrr.xn--85-psd", + "output": "xn--zca266bwrr.xn--85-psd" + }, + { + "input": "\u00df\u09c1\u1ded.\u062085", + "output": "xn--zca266bwrr.xn--85-psd" + }, + { + "input": "SS\u09c1\u1ded\u3002\u06208\u2085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "ss\u09c1\u1ded\u3002\u06208\u2085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "Ss\u09c1\u1ded\u3002\u06208\u2085", + "output": "xn--ss-e2f077r.xn--85-psd" + }, + { + "input": "\ufe0d\u0a9b\u3002\u5d68", + "output": "xn--6dc.xn--tot" + }, + { + "input": "xn--6dc.xn--tot", + "output": "xn--6dc.xn--tot" + }, + { + "input": "\u0a9b.\u5d68", + "output": "xn--6dc.xn--tot" + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "-\u200c\u2499\ud802\udee5\uff61\ud836\ude35", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored)", + "input": "-\u200c18.\ud802\udee5\u3002\ud836\ude35", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "-18.xn--rx9c.xn--382h", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored)", + "input": "xn---18-9m0a.xn--rx9c.xn--382h", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----ddps939g.xn--382h", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "xn----sgn18r3191a.xn--382h", + "output": null + }, + { + "comment": "V7", + "input": "\ufe05\ufe12\u3002\ud858\udc3e\u1ce0", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\ufe05\u3002\u3002\ud858\udc3e\u1ce0", + "output": "..xn--t6f5138v" + }, + { + "comment": "A4_2 (ignored)", + "input": "..xn--t6f5138v", + "output": "..xn--t6f5138v" + }, + { + "comment": "V7", + "input": "xn--y86c.xn--t6f5138v", + "output": null + }, + { + "input": "xn--t6f5138v", + "output": "xn--t6f5138v" + }, + { + "input": "\ud858\udc3e\u1ce0", + "output": "xn--t6f5138v" + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-\u00df\u200c\u2260", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-\u00df\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-\u00df\u200c\u2260", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-\u00df\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-SS\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-SS\u200c\u2260", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-ss\u200c\u2260", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-ss\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-Ss\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f.-Ss\u200c\u2260", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--u836e.xn---ss-gl2a", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn--u836e.xn---ss-cn0at5l", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn--u836e.xn----qfa750ve7b", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-SS\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-SS\u200c\u2260", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-ss\u200c\u2260", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-ss\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-Ss\u200c=\u0338", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\udb41\udd4f\uff0e-Ss\u200c\u2260", + "output": null + }, + { + "comment": "C1", + "input": "\u1859\u200c\uff61\u226f\ud800\udef2\u2260", + "output": null + }, + { + "comment": "C1", + "input": "\u1859\u200c\uff61>\u0338\ud800\udef2=\u0338", + "output": null + }, + { + "comment": "C1", + "input": "\u1859\u200c\u3002\u226f\ud800\udef2\u2260", + "output": null + }, + { + "comment": "C1", + "input": "\u1859\u200c\u3002>\u0338\ud800\udef2=\u0338", + "output": null + }, + { + "input": "xn--p8e.xn--1ch3a7084l", + "output": "xn--p8e.xn--1ch3a7084l" + }, + { + "input": "\u1859.\u226f\ud800\udef2\u2260", + "output": "xn--p8e.xn--1ch3a7084l" + }, + { + "input": "\u1859.>\u0338\ud800\udef2=\u0338", + "output": "xn--p8e.xn--1ch3a7084l" + }, + { + "comment": "C1", + "input": "xn--p8e650b.xn--1ch3a7084l", + "output": null + }, + { + "comment": "V7", + "input": "\uda7b\udd5b\u0613.\u10b5", + "output": null + }, + { + "comment": "V7", + "input": "\uda7b\udd5b\u0613.\u2d15", + "output": null + }, + { + "comment": "V7", + "input": "xn--1fb94204l.xn--dlj", + "output": null + }, + { + "comment": "V7", + "input": "xn--1fb94204l.xn--tnd", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\udb40\udd37\uff61\uda09\udc41", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\udb40\udd37\u3002\uda09\udc41", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--w720c", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug.xn--w720c", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2488\u0dd6\u7105.\udb1e\udc59\u200d\ua85f", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "1.\u0dd6\u7105.\udb1e\udc59\u200d\ua85f", + "output": null + }, + { + "comment": "V6; V7", + "input": "1.xn--t1c6981c.xn--4c9a21133d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "1.xn--t1c6981c.xn--1ugz184c9lw7i", + "output": null + }, + { + "comment": "V7", + "input": "xn--t1c337io97c.xn--4c9a21133d", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--t1c337io97c.xn--1ugz184c9lw7i", + "output": null + }, + { + "comment": "V6", + "input": "\ud804\uddc0\u258d.\u205e\u1830", + "output": null + }, + { + "comment": "V6", + "input": "xn--9zh3057f.xn--j7e103b", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "-3.\u200d\u30cc\u1895", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "-3.xn--fbf115j", + "output": "-3.xn--fbf115j" + }, + { + "comment": "C2; V3 (ignored)", + "input": "-3.xn--fbf739aq5o", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u2132\u17d2\u200d\uff61\u2260\u200d\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u2132\u17d2\u200d\uff61=\u0338\u200d\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u2132\u17d2\u200d\u3002\u2260\u200d\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u2132\u17d2\u200d\u3002=\u0338\u200d\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u214e\u17d2\u200d\u3002=\u0338\u200d\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u214e\u17d2\u200d\u3002\u2260\u200d\u200c", + "output": null + }, + { + "input": "xn--u4e969b.xn--1ch", + "output": "xn--u4e969b.xn--1ch" + }, + { + "input": "\u214e\u17d2.\u2260", + "output": "xn--u4e969b.xn--1ch" + }, + { + "input": "\u214e\u17d2.=\u0338", + "output": "xn--u4e969b.xn--1ch" + }, + { + "input": "\u2132\u17d2.=\u0338", + "output": "xn--u4e969b.xn--1ch" + }, + { + "input": "\u2132\u17d2.\u2260", + "output": "xn--u4e969b.xn--1ch" + }, + { + "comment": "C1; C2", + "input": "xn--u4e823bq1a.xn--0ugb89o", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u214e\u17d2\u200d\uff61=\u0338\u200d\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u214e\u17d2\u200d\uff61\u2260\u200d\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--u4e319b.xn--1ch", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--u4e823bcza.xn--0ugb89o", + "output": null + }, + { + "comment": "V7", + "input": "\ud9a9\udd2f\u0fa8\uff0e\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\ud9a9\udd2f\u0fa8\uff0e>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\ud9a9\udd2f\u0fa8.\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\ud9a9\udd2f\u0fa8.>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "xn--4fd57150h.xn--hdh", + "output": null + }, + { + "comment": "V6", + "input": "\ud802\ude3f\udb40\udd8c\u9e2e\ud805\udeb6.\u03c2", + "output": null + }, + { + "comment": "V6", + "input": "\ud802\ude3f\udb40\udd8c\u9e2e\ud805\udeb6.\u03a3", + "output": null + }, + { + "comment": "V6", + "input": "\ud802\ude3f\udb40\udd8c\u9e2e\ud805\udeb6.\u03c3", + "output": null + }, + { + "comment": "V6", + "input": "xn--l76a726rt2h.xn--4xa", + "output": null + }, + { + "comment": "V6", + "input": "xn--l76a726rt2h.xn--3xa", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u03c2-\u3002\u200c\ud835\udfed-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u03c2-\u3002\u200c1-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u03a3-\u3002\u200c1-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u03c3-\u3002\u200c1-", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "xn----zmb.1-", + "output": "xn----zmb.1-" + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn----zmb.xn--1--i1t", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn----xmb.xn--1--i1t", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u03a3-\u3002\u200c\ud835\udfed-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u03c3-\u3002\u200c\ud835\udfed-", + "output": null + }, + { + "comment": "V6", + "input": "\u1734-\u0ce2\uff0e\udb40\udd29\u10a4", + "output": null + }, + { + "comment": "V6", + "input": "\u1734-\u0ce2.\udb40\udd29\u10a4", + "output": null + }, + { + "comment": "V6", + "input": "\u1734-\u0ce2.\udb40\udd29\u2d04", + "output": null + }, + { + "comment": "V6", + "input": "xn----ggf830f.xn--vkj", + "output": null + }, + { + "comment": "V6", + "input": "\u1734-\u0ce2\uff0e\udb40\udd29\u2d04", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn----ggf830f.xn--cnd", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u200d\u3002\ud838\udc18\u2488\ua84d\u64c9", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u3002\ud838\udc181.\ua84d\u64c9", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": ".xn--1-1p4r.xn--s7uv61m", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--1ug.xn--1-1p4r.xn--s7uv61m", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": ".xn--tsh026uql4bew9p", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--1ug.xn--tsh026uql4bew9p", + "output": null + }, + { + "comment": "V7", + "input": "\u2ad0\uff61\u10c0-\udacd\udc22", + "output": null + }, + { + "comment": "V7", + "input": "\u2ad0\u3002\u10c0-\udacd\udc22", + "output": null + }, + { + "comment": "V7", + "input": "\u2ad0\u3002\u2d20-\udacd\udc22", + "output": null + }, + { + "comment": "V7", + "input": "xn--r3i.xn----2wst7439i", + "output": null + }, + { + "comment": "V7", + "input": "\u2ad0\uff61\u2d20-\udacd\udc22", + "output": null + }, + { + "comment": "V7", + "input": "xn--r3i.xn----z1g58579u", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udc42\u25ca\uff0e\u299f\u2220", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udc42\u25ca.\u299f\u2220", + "output": null + }, + { + "comment": "V6", + "input": "xn--01h3338f.xn--79g270a", + "output": null + }, + { + "comment": "V7", + "input": "\ud5c1\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba\ud835\udfdc", + "output": null + }, + { + "comment": "V7", + "input": "\u1112\u1164\u11bc\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba\ud835\udfdc", + "output": null + }, + { + "comment": "V7", + "input": "\ud5c1\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba4", + "output": null + }, + { + "comment": "V7", + "input": "\u1112\u1164\u11bc\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba4", + "output": null + }, + { + "comment": "V7", + "input": "xn--o4c1723h8g85gt4ya.xn--4-dvc", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua953.\u033d\ud804\udcbd\u998b", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--3j9a.xn--bua0708eqzrd", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udae2\udedd\uda69\udef8\u200d\uff61\u4716", + "output": null + }, + { + "comment": "C2; V7", + "input": "\udae2\udedd\uda69\udef8\u200d\u3002\u4716", + "output": null + }, + { + "comment": "V7", + "input": "xn--g138cxw05a.xn--k0o", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug30527h9mxi.xn--k0o", + "output": null + }, + { + "comment": "C2; U1 (ignored)", + "input": "\u186f\u2689\u59f6\ud83c\udd09\uff0e\u06f7\u200d\ud83c\udfaa\u200d", + "output": null + }, + { + "comment": "C2; U1 (ignored)", + "input": "\u186f\u2689\u59f68,.\u06f7\u200d\ud83c\udfaa\u200d", + "output": null + }, + { + "comment": "U1 (ignored)", + "input": "xn--8,-g9oy26fzu4d.xn--kmb6733w", + "output": "xn--8,-g9oy26fzu4d.xn--kmb6733w" + }, + { + "comment": "C2; U1 (ignored)", + "input": "xn--8,-g9oy26fzu4d.xn--kmb859ja94998b", + "output": null + }, + { + "comment": "V7", + "input": "xn--c9e433epi4b3j20a.xn--kmb6733w", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--c9e433epi4b3j20a.xn--kmb859ja94998b", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "\u135f\u1848\u200c\uff0e\ufe12-\ud81b\udf90-", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored); A4_2 (ignored)", + "input": "\u135f\u1848\u200c.\u3002-\ud81b\udf90-", + "output": null + }, + { + "comment": "V6; V3 (ignored); A4_2 (ignored)", + "input": "xn--b7d82w..xn-----pe4u", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored); A4_2 (ignored)", + "input": "xn--b7d82wo4h..xn-----pe4u", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--b7d82w.xn-----c82nz547a", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "xn--b7d82wo4h.xn-----c82nz547a", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\ud836\ude5c\u3002-\u0b4d\u10ab", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\ud836\ude5c\u3002-\u0b4d\u2d0b", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--792h.xn----bse820x", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--792h.xn----bse632b", + "output": null + }, + { + "comment": "C1", + "input": "\ud835\udff5\u9681\u2bee\uff0e\u180d\u200c", + "output": null + }, + { + "comment": "C1", + "input": "9\u9681\u2bee.\u180d\u200c", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--9-mfs8024b.", + "output": "xn--9-mfs8024b." + }, + { + "comment": "A4_2 (ignored)", + "input": "9\u9681\u2bee.", + "output": "xn--9-mfs8024b." + }, + { + "comment": "C1", + "input": "xn--9-mfs8024b.xn--0ug", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u1bac\u10ac\u200c\u0325\u3002\ud835\udff8", + "output": null + }, + { + "comment": "V6", + "input": "xn--mta176jjjm.c", + "output": null + }, + { + "comment": "C1; V6", + "input": "xn--mta176j97cl2q.c", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u1bac\u2d0c\u200c\u0325\u3002\ud835\udff8", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--mta930emri.c", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--mta930emribme.c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\udb40\udd01\u035f\u2fb6\uff61\u2087\ufe12\ub207\u226e", + "output": null + }, + { + "comment": "V6; V7", + "input": "\udb40\udd01\u035f\u2fb6\uff61\u2087\ufe12\u1102\u116e\u11aa<\u0338", + "output": null + }, + { + "comment": "V6", + "input": "\udb40\udd01\u035f\u98db\u30027\u3002\ub207\u226e", + "output": null + }, + { + "comment": "V6", + "input": "\udb40\udd01\u035f\u98db\u30027\u3002\u1102\u116e\u11aa<\u0338", + "output": null + }, + { + "comment": "V6", + "input": "xn--9ua0567e.7.xn--gdh6767c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--9ua0567e.xn--7-ngou006d1ttc", + "output": null + }, + { + "input": "xn--2ib43l.xn--te6h", + "output": "xn--2ib43l.xn--te6h" + }, + { + "input": "\u067d\u0943.\ud83a\udd35", + "output": "xn--2ib43l.xn--te6h" + }, + { + "input": "\u067d\u0943.\ud83a\udd13", + "output": "xn--2ib43l.xn--te6h" + }, + { + "comment": "C1; V6", + "input": "\u200c\u3002\uffa0\u0f84\u0f96", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u200c\u3002\u1160\u0f84\u0f96", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": ".xn--3ed0b", + "output": null + }, + { + "comment": "C1; V6", + "input": "xn--0ug.xn--3ed0b", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--3ed0b20h", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug.xn--3ed0b20h", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--3ed0by082k", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug.xn--3ed0by082k", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u226f\ud9f5\ude05\uff0e\u200d\ud800\udd7c\uda88\udddb", + "output": null + }, + { + "comment": "C2; V7", + "input": ">\u0338\ud9f5\ude05\uff0e\u200d\ud800\udd7c\uda88\udddb", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u226f\ud9f5\ude05.\u200d\ud800\udd7c\uda88\udddb", + "output": null + }, + { + "comment": "C2; V7", + "input": ">\u0338\ud9f5\ude05.\u200d\ud800\udd7c\uda88\udddb", + "output": null + }, + { + "comment": "V7", + "input": "xn--hdh84488f.xn--xy7cw2886b", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--hdh84488f.xn--1ug8099fbjp4e", + "output": null + }, + { + "input": "\ua9d0\u04c0\u1baa\u08f6\uff0e\ub235", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04c0\u1baa\u08f6\uff0e\u1102\u116f\u11bc", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04c0\u1baa\u08f6.\ub235", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04c0\u1baa\u08f6.\u1102\u116f\u11bc", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04cf\u1baa\u08f6.\u1102\u116f\u11bc", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04cf\u1baa\u08f6.\ub235", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "xn--s5a04sn4u297k.xn--2e1b", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04cf\u1baa\u08f6\uff0e\u1102\u116f\u11bc", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "input": "\ua9d0\u04cf\u1baa\u08f6\uff0e\ub235", + "output": "xn--s5a04sn4u297k.xn--2e1b" + }, + { + "comment": "V7", + "input": "xn--d5a07sn4u297k.xn--2e1b", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua8ea\uff61\ud818\udd3f\ud804\uddbe\udb40\uddd7", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua8ea\u3002\ud818\udd3f\ud804\uddbe\udb40\uddd7", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--3g9a.xn--ud1dz07k", + "output": null + }, + { + "comment": "V7", + "input": "\udadd\udcd3\ud805\udeb3\u3002\ud903\uddff\u226f\u2f87", + "output": null + }, + { + "comment": "V7", + "input": "\udadd\udcd3\ud805\udeb3\u3002\ud903\uddff>\u0338\u2f87", + "output": null + }, + { + "comment": "V7", + "input": "\udadd\udcd3\ud805\udeb3\u3002\ud903\uddff\u226f\u821b", + "output": null + }, + { + "comment": "V7", + "input": "\udadd\udcd3\ud805\udeb3\u3002\ud903\uddff>\u0338\u821b", + "output": null + }, + { + "comment": "V7", + "input": "xn--3e2d79770c.xn--hdh0088abyy1c", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--9hb7344k.", + "output": "xn--9hb7344k." + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud802\udec7\u0661.", + "output": "xn--9hb7344k." + }, + { + "comment": "C1; V7", + "input": "\ud944\udd48\u782a\u226f\u1891\uff61\u226f\ud836\ude5a\uda0f\udd14\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ud944\udd48\u782a>\u0338\u1891\uff61>\u0338\ud836\ude5a\uda0f\udd14\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ud944\udd48\u782a\u226f\u1891\u3002\u226f\ud836\ude5a\uda0f\udd14\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ud944\udd48\u782a>\u0338\u1891\u3002>\u0338\ud836\ude5a\uda0f\udd14\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--bbf561cf95e57y3e.xn--hdh0834o7mj6b", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--bbf561cf95e57y3e.xn--0ugz6gc910ejro8c", + "output": null + }, + { + "comment": "V6", + "input": "\u10c5.\ud804\udd33\u32b8", + "output": null + }, + { + "comment": "V6", + "input": "\u10c5.\ud804\udd3343", + "output": null + }, + { + "comment": "V6", + "input": "\u2d25.\ud804\udd3343", + "output": null + }, + { + "comment": "V6", + "input": "xn--tlj.xn--43-274o", + "output": null + }, + { + "comment": "V6", + "input": "\u2d25.\ud804\udd33\u32b8", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--9nd.xn--43-274o", + "output": null + }, + { + "comment": "V7", + "input": "\ud91e\udea8\udb40\udd09\uffa0\u0fb7.\ud9a1\udfb0\ua953", + "output": null + }, + { + "comment": "V7", + "input": "\ud91e\udea8\udb40\udd09\u1160\u0fb7.\ud9a1\udfb0\ua953", + "output": null + }, + { + "comment": "V7", + "input": "xn--kgd72212e.xn--3j9au7544a", + "output": null + }, + { + "comment": "V7", + "input": "xn--kgd36f9z57y.xn--3j9au7544a", + "output": null + }, + { + "comment": "V7", + "input": "xn--kgd7493jee34a.xn--3j9au7544a", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u0618.\u06f3\u200c\ua953", + "output": null + }, + { + "comment": "V6", + "input": "xn--6fb.xn--gmb0524f", + "output": null + }, + { + "comment": "C1; V6", + "input": "xn--6fb.xn--gmb469jjf1h", + "output": null + }, + { + "comment": "V7", + "input": "\u184c\uff0e\ufe12\u1891", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\u184c.\u3002\u1891", + "output": "xn--c8e..xn--bbf" + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--c8e..xn--bbf", + "output": "xn--c8e..xn--bbf" + }, + { + "comment": "V7", + "input": "xn--c8e.xn--bbf9168i", + "output": null + }, + { + "comment": "V7", + "input": "\ud83b\uddcf\u3002\u1822\uda0d\ude06", + "output": null + }, + { + "comment": "V7", + "input": "xn--hd7h.xn--46e66060j", + "output": null + }, + { + "comment": "V7", + "input": "\ud9f0\uded4\udb40\udd8e\udb40\udd97\ud807\udc95\u3002\u226e", + "output": null + }, + { + "comment": "V7", + "input": "\ud9f0\uded4\udb40\udd8e\udb40\udd97\ud807\udc95\u3002<\u0338", + "output": null + }, + { + "comment": "V7", + "input": "xn--4m3dv4354a.xn--gdh", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\udb40\udda6.\u08e3\u6680\u2260", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\udb40\udda6.\u08e3\u6680=\u0338", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": ".xn--m0b461k3g2c", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u40b9\udbb9\udd85\ud800\udee6\uff0e\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u40b9\udbb9\udd85\ud800\udee6.\u200d", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--0on3543c5981i.", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--0on3543c5981i.xn--1ug", + "output": null + }, + { + "comment": "V7", + "input": "\ufe12\uff61\u10a3\u226f", + "output": null + }, + { + "comment": "V7", + "input": "\ufe12\uff61\u10a3>\u0338", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\u3002\u3002\u10a3\u226f", + "output": "..xn--hdh782b" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u3002\u3002\u10a3>\u0338", + "output": "..xn--hdh782b" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u3002\u3002\u2d03>\u0338", + "output": "..xn--hdh782b" + }, + { + "comment": "A4_2 (ignored)", + "input": "\u3002\u3002\u2d03\u226f", + "output": "..xn--hdh782b" + }, + { + "comment": "A4_2 (ignored)", + "input": "..xn--hdh782b", + "output": "..xn--hdh782b" + }, + { + "comment": "V7", + "input": "\ufe12\uff61\u2d03>\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\ufe12\uff61\u2d03\u226f", + "output": null + }, + { + "comment": "V7", + "input": "xn--y86c.xn--hdh782b", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "..xn--bnd622g", + "output": null + }, + { + "comment": "V7", + "input": "xn--y86c.xn--bnd622g", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u10c1-\udb40\udc5d\uff61\u2260-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u10c1-\udb40\udc5d\uff61=\u0338-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u10c1-\udb40\udc5d\u3002\u2260-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u10c1-\udb40\udc5d\u3002=\u0338-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u2d21-\udb40\udc5d\u3002=\u0338-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u2d21-\udb40\udc5d\u3002\u2260-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "xn----4wsr321ay823p.xn----tfot873s", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u2d21-\udb40\udc5d\uff61=\u0338-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "\u7b83\u2d21-\udb40\udc5d\uff61\u2260-\ud83e\udd16", + "output": null + }, + { + "comment": "V7", + "input": "xn----11g3013fy8x5m.xn----tfot873s", + "output": null + }, + { + "input": "\u07e5.\u06b5", + "output": "xn--dtb.xn--okb" + }, + { + "input": "xn--dtb.xn--okb", + "output": "xn--dtb.xn--okb" + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--3e6h", + "output": ".xn--3e6h" + }, + { + "input": "xn--3e6h", + "output": "xn--3e6h" + }, + { + "input": "\ud83a\udd3f", + "output": "xn--3e6h" + }, + { + "input": "\ud83a\udd1d", + "output": "xn--3e6h" + }, + { + "comment": "C1; V6; V3 (ignored)", + "input": "\u103a\u200d\u200c\u3002-\u200c", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--bkd.-", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored)", + "input": "xn--bkd412fca.xn----sgn", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ufe12\uff61\u1b44\u1849", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\u3002\u3002\u1b44\u1849", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "..xn--87e93m", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--y86c.xn--87e93m", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "-\u1bab\ufe12\u200d.\ud90b\udd88\ud957\ude53", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "-\u1bab\u3002\u200d.\ud90b\udd88\ud957\ude53", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "xn----qml..xn--x50zy803a", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----qml.xn--1ug.xn--x50zy803a", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----qml1407i.xn--x50zy803a", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----qmlv7tw180a.xn--x50zy803a", + "output": null + }, + { + "comment": "V7", + "input": "\udb42\uddae.\u226f\ud838\udc06", + "output": null + }, + { + "comment": "V7", + "input": "\udb42\uddae.>\u0338\ud838\udc06", + "output": null + }, + { + "comment": "V7", + "input": "xn--t546e.xn--hdh5166o", + "output": null + }, + { + "input": "\u06b9\uff0e\u1873\u115f", + "output": "xn--skb.xn--g9e" + }, + { + "input": "\u06b9.\u1873\u115f", + "output": "xn--skb.xn--g9e" + }, + { + "input": "xn--skb.xn--g9e", + "output": "xn--skb.xn--g9e" + }, + { + "input": "\u06b9.\u1873", + "output": "xn--skb.xn--g9e" + }, + { + "comment": "V7", + "input": "xn--skb.xn--osd737a", + "output": null + }, + { + "comment": "V7", + "input": "\u3a1b\ud823\udc4e.\ufe12\ud835\udfd5\u0d01", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\u3a1b\ud823\udc4e.\u30027\u0d01", + "output": "xn--mbm8237g..xn--7-7hf" + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--mbm8237g..xn--7-7hf", + "output": "xn--mbm8237g..xn--7-7hf" + }, + { + "comment": "V7", + "input": "xn--mbm8237g.xn--7-7hf1526p", + "output": null + }, + { + "comment": "C1", + "input": "\u00df\u200c\uaaf6\u18a5\uff0e\u22b6\u10c1\u10b6", + "output": null + }, + { + "comment": "C1", + "input": "\u00df\u200c\uaaf6\u18a5.\u22b6\u10c1\u10b6", + "output": null + }, + { + "comment": "C1", + "input": "\u00df\u200c\uaaf6\u18a5.\u22b6\u2d21\u2d16", + "output": null + }, + { + "comment": "C1", + "input": "SS\u200c\uaaf6\u18a5.\u22b6\u10c1\u10b6", + "output": null + }, + { + "comment": "C1", + "input": "ss\u200c\uaaf6\u18a5.\u22b6\u2d21\u2d16", + "output": null + }, + { + "comment": "C1", + "input": "Ss\u200c\uaaf6\u18a5.\u22b6\u10c1\u2d16", + "output": null + }, + { + "input": "xn--ss-4epx629f.xn--ifh802b6a", + "output": "xn--ss-4epx629f.xn--ifh802b6a" + }, + { + "input": "ss\uaaf6\u18a5.\u22b6\u2d21\u2d16", + "output": "xn--ss-4epx629f.xn--ifh802b6a" + }, + { + "input": "SS\uaaf6\u18a5.\u22b6\u10c1\u10b6", + "output": "xn--ss-4epx629f.xn--ifh802b6a" + }, + { + "input": "Ss\uaaf6\u18a5.\u22b6\u10c1\u2d16", + "output": "xn--ss-4epx629f.xn--ifh802b6a" + }, + { + "comment": "C1", + "input": "xn--ss-4ep585bkm5p.xn--ifh802b6a", + "output": null + }, + { + "comment": "C1", + "input": "xn--zca682johfi89m.xn--ifh802b6a", + "output": null + }, + { + "comment": "C1", + "input": "\u00df\u200c\uaaf6\u18a5\uff0e\u22b6\u2d21\u2d16", + "output": null + }, + { + "comment": "C1", + "input": "SS\u200c\uaaf6\u18a5\uff0e\u22b6\u10c1\u10b6", + "output": null + }, + { + "comment": "C1", + "input": "ss\u200c\uaaf6\u18a5\uff0e\u22b6\u2d21\u2d16", + "output": null + }, + { + "comment": "C1", + "input": "Ss\u200c\uaaf6\u18a5\uff0e\u22b6\u10c1\u2d16", + "output": null + }, + { + "comment": "V7", + "input": "xn--ss-4epx629f.xn--5nd703gyrh", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--ss-4ep585bkm5p.xn--5nd703gyrh", + "output": null + }, + { + "comment": "V7", + "input": "xn--ss-4epx629f.xn--undv409k", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--ss-4ep585bkm5p.xn--undv409k", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--zca682johfi89m.xn--undv409k", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u3002\u03c2\udb40\udc49", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u3002\u03a3\udb40\udc49", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u3002\u03c3\udb40\udc49", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--4xa24344p", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug.xn--4xa24344p", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug.xn--3xa44344p", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u2492\uda61\ude19\uda8f\udce0\ud805\udcc0.-\udb3a\udc4a", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "11.\uda61\ude19\uda8f\udce0\ud805\udcc0.-\udb3a\udc4a", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "11.xn--uz1d59632bxujd.xn----x310m", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--3shy698frsu9dt1me.xn----x310m", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "-\uff61\u200d", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "-\u3002\u200d", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "-.", + "output": "-." + }, + { + "comment": "C2; V3 (ignored)", + "input": "-.xn--1ug", + "output": null + }, + { + "comment": "V7", + "input": "\u126c\uda12\udc3c\ud8c5\uddf6\uff61\ud802\ude2c\ud835\udfe0", + "output": null + }, + { + "comment": "V7", + "input": "\u126c\uda12\udc3c\ud8c5\uddf6\u3002\ud802\ude2c8", + "output": null + }, + { + "comment": "V7", + "input": "xn--d0d41273c887z.xn--8-ob5i", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u03c2\u200d-.\u10c3\ud859\udfd9", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u03c2\u200d-.\u2d23\ud859\udfd9", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u03a3\u200d-.\u10c3\ud859\udfd9", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u03c3\u200d-.\u2d23\ud859\udfd9", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "xn----zmb.xn--rlj2573p", + "output": "xn----zmb.xn--rlj2573p" + }, + { + "comment": "C2; V3 (ignored)", + "input": "xn----zmb048s.xn--rlj2573p", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "xn----xmb348s.xn--rlj2573p", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----zmb.xn--7nd64871a", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----zmb048s.xn--7nd64871a", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----xmb348s.xn--7nd64871a", + "output": null + }, + { + "input": "\u2260\u3002\ud83d\udfb3\ud835\udff2", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "input": "=\u0338\u3002\ud83d\udfb3\ud835\udff2", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "input": "\u2260\u3002\ud83d\udfb36", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "input": "=\u0338\u3002\ud83d\udfb36", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "input": "xn--1ch.xn--6-dl4s", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "input": "\u2260.\ud83d\udfb36", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "input": "=\u0338.\ud83d\udfb36", + "output": "xn--1ch.xn--6-dl4s" + }, + { + "comment": "V7", + "input": "\udad6\udf3d.\u8814", + "output": null + }, + { + "comment": "V7", + "input": "xn--g747d.xn--xl2a", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u08e6\u200d\uff0e\ubf3d", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u08e6\u200d\uff0e\u1108\u1168\u11c0", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u08e6\u200d.\ubf3d", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u08e6\u200d.\u1108\u1168\u11c0", + "output": null + }, + { + "comment": "V6", + "input": "xn--p0b.xn--e43b", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--p0b869i.xn--e43b", + "output": null + }, + { + "comment": "V7", + "input": "\ud8f6\ude3d\uff0e\ud8ef\ude15", + "output": null + }, + { + "comment": "V7", + "input": "\ud8f6\ude3d.\ud8ef\ude15", + "output": null + }, + { + "comment": "V7", + "input": "xn--pr3x.xn--rv7w", + "output": null + }, + { + "comment": "V7", + "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea7\u2084\u10ab\ud8cb\ude6b", + "output": null + }, + { + "comment": "V7", + "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea74\u10ab\ud8cb\ude6b", + "output": null + }, + { + "comment": "V7", + "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea74\u2d0b\ud8cb\ude6b", + "output": null + }, + { + "comment": "V7", + "input": "xn--039c42bq865a.xn--4-wvs27840bnrzm", + "output": null + }, + { + "comment": "V7", + "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea7\u2084\u2d0b\ud8cb\ude6b", + "output": null + }, + { + "comment": "V7", + "input": "xn--039c42bq865a.xn--4-t0g49302fnrzm", + "output": null + }, + { + "comment": "V6", + "input": "\ud835\udfd3\u3002\u06d7", + "output": null + }, + { + "comment": "V6", + "input": "5\u3002\u06d7", + "output": null + }, + { + "comment": "V6", + "input": "5.xn--nlb", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\udaab\ude29.\u2f95", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\udaab\ude29.\u8c37", + "output": null + }, + { + "comment": "V7", + "input": "xn--i183d.xn--6g3a", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug26167i.xn--6g3a", + "output": null + }, + { + "comment": "C1; C2; V7; V3 (ignored)", + "input": "\ufe12\udafb\udc07\u200d.-\u073c\u200c", + "output": null + }, + { + "comment": "C1; C2; V7; V3 (ignored); A4_2 (ignored)", + "input": "\u3002\udafb\udc07\u200d.-\u073c\u200c", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": ".xn--hh50e.xn----t2c", + "output": null + }, + { + "comment": "C1; C2; V7; V3 (ignored); A4_2 (ignored)", + "input": ".xn--1ug05310k.xn----t2c071q", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--y86c71305c.xn----t2c", + "output": null + }, + { + "comment": "C1; C2; V7; V3 (ignored)", + "input": "xn--1ug1658ftw26f.xn----t2c071q", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\uff0e\ud835\udfd7", + "output": null + }, + { + "comment": "C2", + "input": "\u200d.j", + "output": null + }, + { + "comment": "C2", + "input": "\u200d.J", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".j", + "output": ".j" + }, + { + "comment": "C2", + "input": "xn--1ug.j", + "output": null + }, + { + "input": "j", + "output": "j" + }, + { + "comment": "C1; V7", + "input": "\u10ad\ud8be\udccd\ua868\u05ae\u3002\u10be\u200c\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u2d0d\ud8be\udccd\ua868\u05ae\u3002\u2d1e\u200c\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--5cb172r175fug38a.xn--mlj", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--5cb172r175fug38a.xn--0uga051h", + "output": null + }, + { + "comment": "V7", + "input": "xn--5cb347co96jug15a.xn--2nd", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--5cb347co96jug15a.xn--2nd059ea", + "output": null + }, + { + "comment": "V7", + "input": "\ud800\udef0\u3002\udb05\udcf1", + "output": null + }, + { + "comment": "V7", + "input": "xn--k97c.xn--q031e", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u10ab\ud89b\udff8\uade4\uff0e\uda40\udd7c\ud835\udfe2\ud72a\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u10ab\ud89b\udff8\u1100\u1172\u11af\uff0e\uda40\udd7c\ud835\udfe2\u1112\u1171\u11b9\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u10ab\ud89b\udff8\uade4.\uda40\udd7c0\ud72a\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u10ab\ud89b\udff8\u1100\u1172\u11af.\uda40\udd7c0\u1112\u1171\u11b9\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u2d0b\ud89b\udff8\u1100\u1172\u11af.\uda40\udd7c0\u1112\u1171\u11b9\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u2d0b\ud89b\udff8\uade4.\uda40\udd7c0\ud72a\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--i0b436pkl2g2h42a.xn--0-8le8997mulr5f", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u2d0b\ud89b\udff8\u1100\u1172\u11af\uff0e\uda40\udd7c\ud835\udfe2\u1112\u1171\u11b9\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u08df\u2d0b\ud89b\udff8\uade4\uff0e\uda40\udd7c\ud835\udfe2\ud72a\u0ae3", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--i0b601b6r7l2hs0a.xn--0-8le8997mulr5f", + "output": null + }, + { + "comment": "V7", + "input": "\u0784\uff0e\ud83a\udc5d\u0601", + "output": null + }, + { + "comment": "V7", + "input": "\u0784.\ud83a\udc5d\u0601", + "output": null + }, + { + "comment": "V7", + "input": "xn--lqb.xn--jfb1808v", + "output": null + }, + { + "comment": "V6", + "input": "\u0acd\u2083.8\ua8c4\u200d\ud83c\udce4", + "output": null + }, + { + "comment": "V6", + "input": "\u0acd3.8\ua8c4\u200d\ud83c\udce4", + "output": null + }, + { + "comment": "V6", + "input": "xn--3-yke.xn--8-sl4et308f", + "output": null + }, + { + "comment": "V6", + "input": "xn--3-yke.xn--8-ugnv982dbkwm", + "output": null + }, + { + "comment": "V7", + "input": "\ua855\u2260\u105e\udb7b\udff1\uff61\ud803\udd67\udb40\udd2b\uffa0", + "output": null + }, + { + "comment": "V7", + "input": "\ua855=\u0338\u105e\udb7b\udff1\uff61\ud803\udd67\udb40\udd2b\uffa0", + "output": null + }, + { + "comment": "V7", + "input": "\ua855\u2260\u105e\udb7b\udff1\u3002\ud803\udd67\udb40\udd2b\u1160", + "output": null + }, + { + "comment": "V7", + "input": "\ua855=\u0338\u105e\udb7b\udff1\u3002\ud803\udd67\udb40\udd2b\u1160", + "output": null + }, + { + "comment": "V7", + "input": "xn--cld333gn31h0158l.xn--3g0d", + "output": null + }, + { + "comment": "C1", + "input": "\u9c4a\u3002\u200c", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--rt6a.", + "output": "xn--rt6a." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u9c4a.", + "output": "xn--rt6a." + }, + { + "comment": "C1", + "input": "xn--rt6a.xn--0ug", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--4-0bd15808a.", + "output": "xn--4-0bd15808a." + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud83a\udd3a\u07cc4.", + "output": "xn--4-0bd15808a." + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud83a\udd18\u07cc4.", + "output": "xn--4-0bd15808a." + }, + { + "comment": "V3 (ignored)", + "input": "-\uff61\u43db", + "output": "-.xn--xco" + }, + { + "comment": "V3 (ignored)", + "input": "-\u3002\u43db", + "output": "-.xn--xco" + }, + { + "comment": "V3 (ignored)", + "input": "-.xn--xco", + "output": "-.xn--xco" + }, + { + "comment": "C1; C2; V7", + "input": "\u200c\ud908\udce0\uff0e\u200d", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200c\ud908\udce0.\u200d", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--dj8y.", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--0ugz7551c.xn--1ug", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud804\uddc0.\udb42\ude31", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--wd1d.xn--k946e", + "output": null + }, + { + "input": "\u2f86\uff0e\ua848\uff15\u226f\u00df", + "output": "xn--tc1a.xn--5-qfa988w745i" + }, + { + "input": "\u2f86\uff0e\ua848\uff15>\u0338\u00df", + "output": "xn--tc1a.xn--5-qfa988w745i" + }, + { + "input": "\u820c.\ua8485\u226f\u00df", + "output": "xn--tc1a.xn--5-qfa988w745i" + }, + { + "input": "\u820c.\ua8485>\u0338\u00df", + "output": "xn--tc1a.xn--5-qfa988w745i" + }, + { + "input": "\u820c.\ua8485>\u0338SS", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u820c.\ua8485\u226fSS", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u820c.\ua8485\u226fss", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u820c.\ua8485>\u0338ss", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u820c.\ua8485>\u0338Ss", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u820c.\ua8485\u226fSs", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "xn--tc1a.xn--5ss-3m2a5009e", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "xn--tc1a.xn--5-qfa988w745i", + "output": "xn--tc1a.xn--5-qfa988w745i" + }, + { + "input": "\u2f86\uff0e\ua848\uff15>\u0338SS", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u2f86\uff0e\ua848\uff15\u226fSS", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u2f86\uff0e\ua848\uff15\u226fss", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u2f86\uff0e\ua848\uff15>\u0338ss", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u2f86\uff0e\ua848\uff15>\u0338Ss", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\u2f86\uff0e\ua848\uff15\u226fSs", + "output": "xn--tc1a.xn--5ss-3m2a5009e" + }, + { + "input": "\ud83a\udd2a.\u03c2", + "output": "xn--ie6h.xn--3xa" + }, + { + "input": "\ud83a\udd08.\u03a3", + "output": "xn--ie6h.xn--4xa" + }, + { + "input": "\ud83a\udd2a.\u03c3", + "output": "xn--ie6h.xn--4xa" + }, + { + "input": "\ud83a\udd08.\u03c3", + "output": "xn--ie6h.xn--4xa" + }, + { + "input": "xn--ie6h.xn--4xa", + "output": "xn--ie6h.xn--4xa" + }, + { + "input": "\ud83a\udd08.\u03c2", + "output": "xn--ie6h.xn--3xa" + }, + { + "input": "xn--ie6h.xn--3xa", + "output": "xn--ie6h.xn--3xa" + }, + { + "input": "\ud83a\udd2a.\u03a3", + "output": "xn--ie6h.xn--4xa" + }, + { + "comment": "C1", + "input": "\u200c\u10ba\uff61\u03c2", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u10ba\u3002\u03c2", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u2d1a\u3002\u03c2", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u10ba\u3002\u03a3", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u2d1a\u3002\u03c3", + "output": null + }, + { + "input": "xn--ilj.xn--4xa", + "output": "xn--ilj.xn--4xa" + }, + { + "input": "\u2d1a.\u03c3", + "output": "xn--ilj.xn--4xa" + }, + { + "input": "\u10ba.\u03a3", + "output": "xn--ilj.xn--4xa" + }, + { + "input": "\u2d1a.\u03c2", + "output": "xn--ilj.xn--3xa" + }, + { + "input": "\u10ba.\u03c2", + "output": "xn--ilj.xn--3xa" + }, + { + "input": "xn--ilj.xn--3xa", + "output": "xn--ilj.xn--3xa" + }, + { + "input": "\u10ba.\u03c3", + "output": "xn--ilj.xn--4xa" + }, + { + "comment": "C1", + "input": "xn--0ug262c.xn--4xa", + "output": null + }, + { + "comment": "C1", + "input": "xn--0ug262c.xn--3xa", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u2d1a\uff61\u03c2", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u10ba\uff61\u03a3", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u2d1a\uff61\u03c3", + "output": null + }, + { + "comment": "V7", + "input": "xn--ynd.xn--4xa", + "output": null + }, + { + "comment": "V7", + "input": "xn--ynd.xn--3xa", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--ynd759e.xn--4xa", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--ynd759e.xn--3xa", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u2f95\u3002\u200c\u0310\ua953\ua84e", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u2f95\u3002\u200c\ua953\u0310\ua84e", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u8c37\u3002\u200c\ua953\u0310\ua84e", + "output": null + }, + { + "comment": "V6", + "input": "xn--6g3a.xn--0sa8175flwa", + "output": null + }, + { + "comment": "C1; C2", + "input": "xn--1ug0273b.xn--0sa359l6n7g13a", + "output": null + }, + { + "input": "\u6dfd\u3002\u183e", + "output": "xn--34w.xn--x7e" + }, + { + "input": "xn--34w.xn--x7e", + "output": "xn--34w.xn--x7e" + }, + { + "input": "\u6dfd.\u183e", + "output": "xn--34w.xn--x7e" + }, + { + "comment": "V6; V7", + "input": "\uda72\ude29\u10b3\u2753\uff61\ud804\udd28", + "output": null + }, + { + "comment": "V6; V7", + "input": "\uda72\ude29\u10b3\u2753\u3002\ud804\udd28", + "output": null + }, + { + "comment": "V6; V7", + "input": "\uda72\ude29\u2d13\u2753\u3002\ud804\udd28", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--8di78qvw32y.xn--k80d", + "output": null + }, + { + "comment": "V6; V7", + "input": "\uda72\ude29\u2d13\u2753\uff61\ud804\udd28", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--rnd896i0j14q.xn--k80d", + "output": null + }, + { + "comment": "V7", + "input": "\u17ff\uff61\ud83a\udf33", + "output": null + }, + { + "comment": "V7", + "input": "\u17ff\u3002\ud83a\udf33", + "output": null + }, + { + "comment": "V7", + "input": "xn--45e.xn--et6h", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u0652\u200d\uff61\u0ccd\ud805\udeb3", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u0652\u200d\u3002\u0ccd\ud805\udeb3", + "output": null + }, + { + "comment": "V6", + "input": "xn--uhb.xn--8tc4527k", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--uhb882k.xn--8tc4527k", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u00df\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6e\u00df", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u00df\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6e\u00df", + "output": null + }, + { + "comment": "V6; V7", + "input": "SS\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6eSS", + "output": null + }, + { + "comment": "V6; V7", + "input": "ss\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6ess", + "output": null + }, + { + "comment": "V6; V7", + "input": "Ss\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6eSs", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ss-jl59biy67d.xn--ss-4d11aw87d", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--zca20040bgrkh.xn--zca3653v86qa", + "output": null + }, + { + "comment": "V6; V7", + "input": "SS\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6eSS", + "output": null + }, + { + "comment": "V6; V7", + "input": "ss\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6ess", + "output": null + }, + { + "comment": "V6; V7", + "input": "Ss\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6eSs", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u3002\u200c", + "output": null + }, + { + "comment": "C1; C2", + "input": "xn--1ug.xn--0ug", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\udb41\udc58\uff0e\udb40\udd2e", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\udb41\udc58.\udb40\udd2e", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--s136e.", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua9b7\udb37\udd59\uba79\u3002\u249b\udb42\ude07", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua9b7\udb37\udd59\u1106\u1167\u11b0\u3002\u249b\udb42\ude07", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua9b7\udb37\udd59\uba79\u300220.\udb42\ude07", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua9b7\udb37\udd59\u1106\u1167\u11b0\u300220.\udb42\ude07", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ym9av13acp85w.20.xn--d846e", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ym9av13acp85w.xn--dth22121k", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\uff61\ufe12", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "\u200c\u3002\u3002", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "..", + "output": ".." + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "xn--0ug..", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--y86c", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug.xn--y86c", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-\ud835\udff9.\u00df-\u200c-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-3.\u00df-\u200c-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-3.SS-\u200c-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-3.ss-\u200c-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-3.Ss-\u200c-", + "output": null + }, + { + "comment": "V2 (ignored); V3 (ignored)", + "input": "xn---3-p9o.ss--", + "output": "xn---3-p9o.ss--" + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn---3-p9o.xn--ss---276a", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn---3-p9o.xn-----fia9303a", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-\ud835\udff9.SS-\u200c-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-\ud835\udff9.ss-\u200c-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u1872-\ud835\udff9.Ss-\u200c-", + "output": null + }, + { + "comment": "V6; V7", + "input": "\udb27\udd9c\u1898\u3002\u1a7f\u2ea2", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ibf35138o.xn--fpfz94g", + "output": null + }, + { + "comment": "V7", + "input": "\uda1c\udda7\ud835\udfef\u3002\u2488\u1a76\ud835\udfda\uda41\ude0c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\uda1c\udda73\u30021.\u1a762\uda41\ude0c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--3-rj42h.1.xn--2-13k96240l", + "output": null + }, + { + "comment": "V7", + "input": "xn--3-rj42h.xn--2-13k746cq465x", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u2085\u2488\u3002\u226f\ud835\udff4\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u2085\u2488\u3002>\u0338\ud835\udff4\u200d", + "output": null + }, + { + "comment": "C2; A4_2 (ignored)", + "input": "\u200d51.\u3002\u226f8\u200d", + "output": null + }, + { + "comment": "C2; A4_2 (ignored)", + "input": "\u200d51.\u3002>\u03388\u200d", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "51..xn--8-ogo", + "output": "51..xn--8-ogo" + }, + { + "comment": "C2; A4_2 (ignored)", + "input": "xn--51-l1t..xn--8-ugn00i", + "output": null + }, + { + "comment": "V7", + "input": "xn--5-ecp.xn--8-ogo", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--5-tgnz5r.xn--8-ugn00i", + "output": null + }, + { + "comment": "V7", + "input": "\ud8bb\uddc2\u0a42\u10aa\ud8c8\udc9f.\u226e", + "output": null + }, + { + "comment": "V7", + "input": "\ud8bb\uddc2\u0a42\u10aa\ud8c8\udc9f.<\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\ud8bb\uddc2\u0a42\u2d0a\ud8c8\udc9f.<\u0338", + "output": null + }, + { + "comment": "V7", + "input": "\ud8bb\uddc2\u0a42\u2d0a\ud8c8\udc9f.\u226e", + "output": null + }, + { + "comment": "V7", + "input": "xn--nbc229o4y27dgskb.xn--gdh", + "output": null + }, + { + "comment": "V7", + "input": "xn--nbc493aro75ggskb.xn--gdh", + "output": null + }, + { + "input": "\ua860\uff0e\u06f2", + "output": "xn--5c9a.xn--fmb" + }, + { + "input": "\ua860.\u06f2", + "output": "xn--5c9a.xn--fmb" + }, + { + "input": "xn--5c9a.xn--fmb", + "output": "xn--5c9a.xn--fmb" + }, + { + "comment": "C1; V6; U1 (ignored)", + "input": "\ua67d\u200c\ud87e\uddf5\ud83c\udd06\uff61\u200c\ud804\udc42\u1b01", + "output": null + }, + { + "comment": "C1; V6; U1 (ignored)", + "input": "\ua67d\u200c\u9723\ud83c\udd06\uff61\u200c\ud804\udc42\u1b01", + "output": null + }, + { + "comment": "C1; V6; U1 (ignored)", + "input": "\ua67d\u200c\u97235,\u3002\u200c\ud804\udc42\u1b01", + "output": null + }, + { + "comment": "V6; U1 (ignored)", + "input": "xn--5,-op8g373c.xn--4sf0725i", + "output": null + }, + { + "comment": "C1; V6; U1 (ignored)", + "input": "xn--5,-i1tz135dnbqa.xn--4sf36u6u4w", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--2q5a751a653w.xn--4sf0725i", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--0ug4208b2vjuk63a.xn--4sf36u6u4w", + "output": null + }, + { + "comment": "V7", + "input": "\u514e\uff61\u183c\udb43\udd1c\ud805\udeb6\ud807\udc3f", + "output": null + }, + { + "comment": "V7", + "input": "\u514e\u3002\u183c\udb43\udd1c\ud805\udeb6\ud807\udc3f", + "output": null + }, + { + "comment": "V7", + "input": "xn--b5q.xn--v7e6041kqqd4m251b", + "output": null + }, + { + "comment": "C2", + "input": "\ud835\udfd9\uff61\u200d\ud835\udff8\u200d\u2077", + "output": null + }, + { + "comment": "C2", + "input": "1\u3002\u200d2\u200d7", + "output": null + }, + { + "input": "1.2h", + "output": "1.2h" + }, + { + "comment": "C2", + "input": "1.xn--27-l1tb", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u1868-\uff61\udb43\udecb\ud835\udff7", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u1868-\u3002\udb43\udecb1", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----z8j.xn--1-5671m", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u10bc\ud9e3\udded\u0f80\u2f87\u3002\u10af\u2640\u200c\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u10bc\ud9e3\udded\u0f80\u821b\u3002\u10af\u2640\u200c\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u2d1c\ud9e3\udded\u0f80\u821b\u3002\u2d0f\u2640\u200c\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--zed372mdj2do3v4h.xn--e5h11w", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--zed372mdj2do3v4h.xn--0uga678bgyh", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u2d1c\ud9e3\udded\u0f80\u2f87\u3002\u2d0f\u2640\u200c\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--zed54dz10wo343g.xn--nnd651i", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--zed54dz10wo343g.xn--nnd089ea464d", + "output": null + }, + { + "comment": "C2; V6", + "input": "\ud804\udc46\ud835\udff0.\u200d", + "output": null + }, + { + "comment": "C2; V6", + "input": "\ud804\udc464.\u200d", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--4-xu7i.", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--4-xu7i.xn--1ug", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u10be\u7640\uff61\ud805\ude3f\u200d\u200c\ubdbc", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u10be\u7640\uff61\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u10be\u7640\u3002\ud805\ude3f\u200d\u200c\ubdbc", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u10be\u7640\u3002\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u2d1e\u7640\u3002\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u2d1e\u7640\u3002\ud805\ude3f\u200d\u200c\ubdbc", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--mlju35u7qx2f.xn--et3bn23n", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--mlju35u7qx2f.xn--0ugb6122js83c", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u2d1e\u7640\uff61\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud97b\udd18\u2d1e\u7640\uff61\ud805\ude3f\u200d\u200c\ubdbc", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--2nd6803c7q37d.xn--et3bn23n", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--2nd6803c7q37d.xn--0ugb6122js83c", + "output": null + }, + { + "comment": "V7", + "input": "\u1843\ud835\udfe7\u226f\u1823\uff0e\u6c01\ud960\udff1\ua06b", + "output": null + }, + { + "comment": "V7", + "input": "\u1843\ud835\udfe7>\u0338\u1823\uff0e\u6c01\ud960\udff1\ua06b", + "output": null + }, + { + "comment": "V7", + "input": "\u18435\u226f\u1823.\u6c01\ud960\udff1\ua06b", + "output": null + }, + { + "comment": "V7", + "input": "\u18435>\u0338\u1823.\u6c01\ud960\udff1\ua06b", + "output": null + }, + { + "comment": "V7", + "input": "xn--5-24jyf768b.xn--lqw213ime95g", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-\ud804\ude36\u248f\uff0e\u248e\ud881\udee2\udb40\udfad", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "-\ud804\ude368..7.\ud881\udee2\udb40\udfad", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "xn---8-bv5o..7.xn--c35nf1622b", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----scp6252h.xn--zshy411yzpx2d", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u10a1\u755d\u200d\uff0e\u226e", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u10a1\u755d\u200d\uff0e<\u0338", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u10a1\u755d\u200d.\u226e", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u10a1\u755d\u200d.<\u0338", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u2d01\u755d\u200d.<\u0338", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u2d01\u755d\u200d.\u226e", + "output": null + }, + { + "input": "xn--skjy82u.xn--gdh", + "output": "xn--skjy82u.xn--gdh" + }, + { + "input": "\u2d01\u755d.\u226e", + "output": "xn--skjy82u.xn--gdh" + }, + { + "input": "\u2d01\u755d.<\u0338", + "output": "xn--skjy82u.xn--gdh" + }, + { + "input": "\u10a1\u755d.<\u0338", + "output": "xn--skjy82u.xn--gdh" + }, + { + "input": "\u10a1\u755d.\u226e", + "output": "xn--skjy82u.xn--gdh" + }, + { + "comment": "C1; C2", + "input": "xn--0ugc160hb36e.xn--gdh", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u2d01\u755d\u200d\uff0e<\u0338", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200c\u2d01\u755d\u200d\uff0e\u226e", + "output": null + }, + { + "comment": "V7", + "input": "xn--8md0962c.xn--gdh", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--8md700fea3748f.xn--gdh", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u0ecb\u200d\uff0e\u9381\udb43\udc11", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u0ecb\u200d.\u9381\udb43\udc11", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--t8c.xn--iz4a43209d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--t8c059f.xn--iz4a43209d", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\ud9e5\udef4.-\u1862\u0592\ud836\ude20", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--ep37b.xn----hec165lho83b", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b\uff0e\u1baa\u03c2\u10a6\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b.\u1baa\u03c2\u10a6\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b.\u1baa\u03c2\u2d06\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b.\u1baa\u03a3\u10a6\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b.\u1baa\u03c3\u2d06\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b.\u1baa\u03a3\u2d06\u200d", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--nu4s.xn--4xa153j7im", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--nu4s.xn--4xa153jk8cs1q", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--nu4s.xn--3xa353jk8cs1q", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b\uff0e\u1baa\u03c2\u2d06\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b\uff0e\u1baa\u03a3\u10a6\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b\uff0e\u1baa\u03c3\u2d06\u200d", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ud8bc\udc2b\uff0e\u1baa\u03a3\u2d06\u200d", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--nu4s.xn--4xa217dxri", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--nu4s.xn--4xa217dxriome", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--nu4s.xn--3xa417dxriome", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\u2488\u200c\uaaec\ufe12\uff0e\u0acd", + "output": null + }, + { + "comment": "C1; V6; A4_2 (ignored)", + "input": "1.\u200c\uaaec\u3002.\u0acd", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "1.xn--sv9a..xn--mfc", + "output": null + }, + { + "comment": "C1; V6; A4_2 (ignored)", + "input": "1.xn--0ug7185c..xn--mfc", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--tsh0720cse8b.xn--mfc", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--0ug78o720myr1c.xn--mfc", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u00df\u200d.\u1bf2\ud8d3\udfbc", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "SS\u200d.\u1bf2\ud8d3\udfbc", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "ss\u200d.\u1bf2\ud8d3\udfbc", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "Ss\u200d.\u1bf2\ud8d3\udfbc", + "output": null + }, + { + "comment": "V6; V7", + "input": "ss.xn--0zf22107b", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--ss-n1t.xn--0zf22107b", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--zca870n.xn--0zf22107b", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udcc2\u200c\u226e.\u226e", + "output": null + }, + { + "comment": "V6", + "input": "\ud805\udcc2\u200c<\u0338.<\u0338", + "output": null + }, + { + "comment": "V6", + "input": "xn--gdhz656g.xn--gdh", + "output": null + }, + { + "comment": "V6", + "input": "xn--0ugy6glz29a.xn--gdh", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud83d\udd7c\uff0e\uffa0", + "output": "xn--my8h." + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud83d\udd7c.\u1160", + "output": "xn--my8h." + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--my8h.", + "output": "xn--my8h." + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud83d\udd7c.", + "output": "xn--my8h." + }, + { + "comment": "V7", + "input": "xn--my8h.xn--psd", + "output": null + }, + { + "comment": "V7", + "input": "xn--my8h.xn--cl7c", + "output": null + }, + { + "comment": "V7", + "input": "\u7215\uda8d\ude51\uff0e\ud835\udff0\u6c17", + "output": null + }, + { + "comment": "V7", + "input": "\u7215\uda8d\ude51.4\u6c17", + "output": null + }, + { + "comment": "V7", + "input": "xn--1zxq3199c.xn--4-678b", + "output": null + }, + { + "comment": "V7; V2 (ignored); V3 (ignored)", + "input": "\udb39\udf43\u3002\uda04\udd83\ud8e6\udc97--", + "output": null + }, + { + "comment": "V7; V2 (ignored); V3 (ignored)", + "input": "xn--2y75e.xn-----1l15eer88n", + "output": null + }, + { + "comment": "V7", + "input": "\u8530\u3002\udb40\udc79\u08dd-\ud804\ude35", + "output": null + }, + { + "comment": "V7", + "input": "xn--sz1a.xn----mrd9984r3dl0i", + "output": null + }, + { + "input": "\u03c2\u10c5\u3002\u075a", + "output": "xn--3xa403s.xn--epb" + }, + { + "input": "\u03c2\u2d25\u3002\u075a", + "output": "xn--3xa403s.xn--epb" + }, + { + "input": "\u03a3\u10c5\u3002\u075a", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "\u03c3\u2d25\u3002\u075a", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "\u03a3\u2d25\u3002\u075a", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "xn--4xa203s.xn--epb", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "\u03c3\u2d25.\u075a", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "\u03a3\u10c5.\u075a", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "\u03a3\u2d25.\u075a", + "output": "xn--4xa203s.xn--epb" + }, + { + "input": "xn--3xa403s.xn--epb", + "output": "xn--3xa403s.xn--epb" + }, + { + "input": "\u03c2\u2d25.\u075a", + "output": "xn--3xa403s.xn--epb" + }, + { + "comment": "V7", + "input": "xn--4xa477d.xn--epb", + "output": null + }, + { + "comment": "V7", + "input": "xn--3xa677d.xn--epb", + "output": null + }, + { + "input": "xn--vkb.xn--08e172a", + "output": "xn--vkb.xn--08e172a" + }, + { + "input": "\u06bc.\u1e8f\u1864", + "output": "xn--vkb.xn--08e172a" + }, + { + "input": "\u06bc.y\u0307\u1864", + "output": "xn--vkb.xn--08e172a" + }, + { + "input": "\u06bc.Y\u0307\u1864", + "output": "xn--vkb.xn--08e172a" + }, + { + "input": "\u06bc.\u1e8e\u1864", + "output": "xn--vkb.xn--08e172a" + }, + { + "input": "xn--pt9c.xn--0kjya", + "output": "xn--pt9c.xn--0kjya" + }, + { + "input": "\ud802\ude57.\u2d09\u2d15", + "output": "xn--pt9c.xn--0kjya" + }, + { + "input": "\ud802\ude57.\u10a9\u10b5", + "output": "xn--pt9c.xn--0kjya" + }, + { + "input": "\ud802\ude57.\u10a9\u2d15", + "output": "xn--pt9c.xn--0kjya" + }, + { + "comment": "V7", + "input": "xn--pt9c.xn--hnd666l", + "output": null + }, + { + "comment": "V7", + "input": "xn--pt9c.xn--hndy", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\u200c\u200c\u3124\uff0e\u032e\udb16\ude11\u09c2", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\u200c\u200c\u3124.\u032e\udb16\ude11\u09c2", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--1fk.xn--vta284a9o563a", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--0uga242k.xn--vta284a9o563a", + "output": null + }, + { + "comment": "V7", + "input": "\u10b4\ud836\ude28\u2083\udb40\udc66\uff0e\ud835\udff3\ud804\udcb9\u0b82", + "output": null + }, + { + "comment": "V7", + "input": "\u10b4\ud836\ude283\udb40\udc66.7\ud804\udcb9\u0b82", + "output": null + }, + { + "comment": "V7", + "input": "\u2d14\ud836\ude283\udb40\udc66.7\ud804\udcb9\u0b82", + "output": null + }, + { + "comment": "V7", + "input": "xn--3-ews6985n35s3g.xn--7-cve6271r", + "output": null + }, + { + "comment": "V7", + "input": "\u2d14\ud836\ude28\u2083\udb40\udc66\uff0e\ud835\udff3\ud804\udcb9\u0b82", + "output": null + }, + { + "comment": "V7", + "input": "xn--3-b1g83426a35t0g.xn--7-cve6271r", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u43c8\u200c\u3002\u200c\u2488\ud986\udc95", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u43c8\u200c\u3002\u200c1.\ud986\udc95", + "output": null + }, + { + "comment": "V7", + "input": "xn--eco.1.xn--ms39a", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug491l.xn--1-rgn.xn--ms39a", + "output": null + }, + { + "comment": "V7", + "input": "xn--eco.xn--tsh21126d", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug491l.xn--0ug88oot66q", + "output": null + }, + { + "comment": "V6", + "input": "\uff11\uaaf6\u00df\ud807\udca5\uff61\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "1\uaaf6\u00df\ud807\udca5\u3002\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "1\uaaf6SS\ud807\udca5\u3002\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "1\uaaf6ss\ud807\udca5\u3002\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "xn--1ss-ir6ln166b.xn--weg", + "output": null + }, + { + "comment": "V6", + "input": "xn--1-qfa2471kdb0d.xn--weg", + "output": null + }, + { + "comment": "V6", + "input": "\uff11\uaaf6SS\ud807\udca5\uff61\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "\uff11\uaaf6ss\ud807\udca5\uff61\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "1\uaaf6Ss\ud807\udca5\u3002\u1dd8", + "output": null + }, + { + "comment": "V6", + "input": "\uff11\uaaf6Ss\ud807\udca5\uff61\u1dd8", + "output": null + }, + { + "comment": "V7", + "input": "xn--3j78f.xn--mkb20b", + "output": null + }, + { + "comment": "V7", + "input": "\ud88a\udd31\u249b\u2fb3\uff0e\ua866\u2488", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\ud88a\udd3120.\u97f3.\ua8661.", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--20-9802c.xn--0w5a.xn--1-eg4e.", + "output": null + }, + { + "comment": "V7", + "input": "xn--dth6033bzbvx.xn--tsh9439b", + "output": null + }, + { + "input": "\u10b5\u3002\u06f0\u226e\u00df\u0745", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "input": "\u10b5\u3002\u06f0<\u0338\u00df\u0745", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "input": "\u2d15\u3002\u06f0<\u0338\u00df\u0745", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "input": "\u2d15\u3002\u06f0\u226e\u00df\u0745", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "input": "\u10b5\u3002\u06f0\u226eSS\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5\u3002\u06f0<\u0338SS\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u2d15\u3002\u06f0<\u0338ss\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u2d15\u3002\u06f0\u226ess\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5\u3002\u06f0\u226eSs\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5\u3002\u06f0<\u0338Ss\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "xn--dlj.xn--ss-jbe65aw27i", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u2d15.\u06f0\u226ess\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u2d15.\u06f0<\u0338ss\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5.\u06f0<\u0338SS\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5.\u06f0\u226eSS\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5.\u06f0\u226eSs\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "\u10b5.\u06f0<\u0338Ss\u0745", + "output": "xn--dlj.xn--ss-jbe65aw27i" + }, + { + "input": "xn--dlj.xn--zca912alh227g", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "input": "\u2d15.\u06f0\u226e\u00df\u0745", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "input": "\u2d15.\u06f0<\u0338\u00df\u0745", + "output": "xn--dlj.xn--zca912alh227g" + }, + { + "comment": "V7", + "input": "xn--tnd.xn--ss-jbe65aw27i", + "output": null + }, + { + "comment": "V7", + "input": "xn--tnd.xn--zca912alh227g", + "output": null + }, + { + "input": "xn--ge6h.xn--oc9a", + "output": "xn--ge6h.xn--oc9a" + }, + { + "input": "\ud83a\udd28.\ua84f", + "output": "xn--ge6h.xn--oc9a" + }, + { + "input": "\ud83a\udd06.\ua84f", + "output": "xn--ge6h.xn--oc9a" + }, + { + "comment": "V7", + "input": "\u2132\udb40\udd7a\ud937\udd52\u3002\u226f\u2f91", + "output": null + }, + { + "comment": "V7", + "input": "\u2132\udb40\udd7a\ud937\udd52\u3002>\u0338\u2f91", + "output": null + }, + { + "comment": "V7", + "input": "\u2132\udb40\udd7a\ud937\udd52\u3002\u226f\u897e", + "output": null + }, + { + "comment": "V7", + "input": "\u2132\udb40\udd7a\ud937\udd52\u3002>\u0338\u897e", + "output": null + }, + { + "comment": "V7", + "input": "\u214e\udb40\udd7a\ud937\udd52\u3002>\u0338\u897e", + "output": null + }, + { + "comment": "V7", + "input": "\u214e\udb40\udd7a\ud937\udd52\u3002\u226f\u897e", + "output": null + }, + { + "comment": "V7", + "input": "xn--73g39298c.xn--hdhz171b", + "output": null + }, + { + "comment": "V7", + "input": "\u214e\udb40\udd7a\ud937\udd52\u3002>\u0338\u2f91", + "output": null + }, + { + "comment": "V7", + "input": "\u214e\udb40\udd7a\ud937\udd52\u3002\u226f\u2f91", + "output": null + }, + { + "comment": "V7", + "input": "xn--f3g73398c.xn--hdhz171b", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c.\u00df\u10a9-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c.\u00df\u2d09-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c.SS\u10a9-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c.ss\u2d09-", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "\u200c.Ss\u2d09-", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": ".xn--ss--bi1b", + "output": ".xn--ss--bi1b" + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn--0ug.xn--ss--bi1b", + "output": null + }, + { + "comment": "C1; V3 (ignored)", + "input": "xn--0ug.xn----pfa2305a", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": ".xn--ss--4rn", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn--0ug.xn--ss--4rn", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn--0ug.xn----pfa042j", + "output": null + }, + { + "input": "\u9f59--\ud835\udff0.\u00df", + "output": "xn----4-p16k.xn--zca" + }, + { + "input": "\u9f59--4.\u00df", + "output": "xn----4-p16k.xn--zca" + }, + { + "input": "\u9f59--4.SS", + "output": "xn----4-p16k.ss" + }, + { + "input": "\u9f59--4.ss", + "output": "xn----4-p16k.ss" + }, + { + "input": "\u9f59--4.Ss", + "output": "xn----4-p16k.ss" + }, + { + "input": "xn----4-p16k.ss", + "output": "xn----4-p16k.ss" + }, + { + "input": "xn----4-p16k.xn--zca", + "output": "xn----4-p16k.xn--zca" + }, + { + "input": "\u9f59--\ud835\udff0.SS", + "output": "xn----4-p16k.ss" + }, + { + "input": "\u9f59--\ud835\udff0.ss", + "output": "xn----4-p16k.ss" + }, + { + "input": "\u9f59--\ud835\udff0.Ss", + "output": "xn----4-p16k.ss" + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udb42\udea2-\u3002\uda2c\udc8f\u226e\ud805\udf2b", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\udb42\udea2-\u3002\uda2c\udc8f<\u0338\ud805\udf2b", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----bh61m.xn--gdhz157g0em1d", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200c\udb40\ude79\u200d\u3002\ud9f3\udfe7\u226e\u10a9", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200c\udb40\ude79\u200d\u3002\ud9f3\udfe7<\u0338\u10a9", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200c\udb40\ude79\u200d\u3002\ud9f3\udfe7<\u0338\u2d09", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200c\udb40\ude79\u200d\u3002\ud9f3\udfe7\u226e\u2d09", + "output": null + }, + { + "comment": "V7", + "input": "xn--3n36e.xn--gdh992byu01p", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--0ugc90904y.xn--gdh992byu01p", + "output": null + }, + { + "comment": "V7", + "input": "xn--3n36e.xn--hnd112gpz83n", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--0ugc90904y.xn--hnd112gpz83n", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u10b0\uff61\ucaa1", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u10b0\uff61\u110d\u1168\u11a8", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u10b0\u3002\ucaa1", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u10b0\u3002\u110d\u1168\u11a8", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u2d10\u3002\u110d\u1168\u11a8", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u2d10\u3002\ucaa1", + "output": null + }, + { + "comment": "V6", + "input": "xn--7kj1858k.xn--pi6b", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u2d10\uff61\u110d\u1168\u11a8", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude9e\u2d10\uff61\ucaa1", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ond3755u.xn--pi6b", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u1845\uff10\u200c\uff61\u23a2\udb52\ude04", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u18450\u200c\u3002\u23a2\udb52\ude04", + "output": null + }, + { + "comment": "V7", + "input": "xn--0-z6j.xn--8lh28773l", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0-z6jy93b.xn--8lh28773l", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200d\u00df", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a9\ua369\u17d3.\u200d\u00df", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a9\ua369\u17d3.\u200dSS", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a9\ua369\u17d3.\u200dss", + "output": null + }, + { + "comment": "V7", + "input": "xn--9-i0j5967eg3qz.ss", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--9-i0j5967eg3qz.xn--ss-l1t", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--9-i0j5967eg3qz.xn--zca770n", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200dSS", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200dss", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a9\ua369\u17d3.\u200dSs", + "output": null + }, + { + "comment": "C2; V7", + "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200dSs", + "output": null + }, + { + "input": "\ua5f7\ud804\udd80.\u075d\ud802\ude52", + "output": "xn--ju8a625r.xn--hpb0073k" + }, + { + "input": "xn--ju8a625r.xn--hpb0073k", + "output": "xn--ju8a625r.xn--hpb0073k" + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u2490\u226f-\u3002\ufe12\uda65\udc63-\ud939\udee0", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u2490>\u0338-\u3002\ufe12\uda65\udc63-\ud939\udee0", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "9.\u226f-\u3002\u3002\uda65\udc63-\ud939\udee0", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "9.>\u0338-\u3002\u3002\uda65\udc63-\ud939\udee0", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "9.xn----ogo..xn----xj54d1s69k", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----ogot9g.xn----n89hl0522az9u2a", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u10af\udb40\udd4b-\uff0e\u200d\u10a9", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u10af\udb40\udd4b-.\u200d\u10a9", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u2d0f\udb40\udd4b-.\u200d\u2d09", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "xn----3vs.xn--0kj", + "output": "xn----3vs.xn--0kj" + }, + { + "comment": "C2; V3 (ignored)", + "input": "xn----3vs.xn--1ug532c", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "\u2d0f\udb40\udd4b-\uff0e\u200d\u2d09", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----00g.xn--hnd", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----00g.xn--hnd399e", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u1714\u3002\udb40\udda3-\ud804\udeea", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--fze.xn----ly8i", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-\uff0e\uda60\udfdc\u05bd\u00df", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-.\uda60\udfdc\u05bd\u00df", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-.\uda60\udfdc\u05bdSS", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-.\uda60\udfdc\u05bdss", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-.\uda60\udfdc\u05bdSs", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----pw5e.xn--ss-7jd10716y", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----pw5e.xn--zca50wfv060a", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-\uff0e\uda60\udfdc\u05bdSS", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-\uff0e\uda60\udfdc\u05bdss", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\uabe8-\uff0e\uda60\udfdc\u05bdSs", + "output": null + }, + { + "comment": "V6", + "input": "\ud835\udfe5\u266e\ud805\udf2b\u08ed\uff0e\u17d2\ud805\udf2b8\udb40\udd8f", + "output": null + }, + { + "comment": "V6", + "input": "3\u266e\ud805\udf2b\u08ed.\u17d2\ud805\udf2b8\udb40\udd8f", + "output": null + }, + { + "comment": "V6", + "input": "xn--3-ksd277tlo7s.xn--8-f0jx021l", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "-\uff61\uda14\udf00\u200d\u2761", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "-\u3002\uda14\udf00\u200d\u2761", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-.xn--nei54421f", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "-.xn--1ug800aq795s", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ud835\udfd3\u2631\ud835\udfd0\uda57\udc35\uff61\ud836\udeae\ud902\udc73", + "output": null + }, + { + "comment": "V6; V7", + "input": "5\u26312\uda57\udc35\u3002\ud836\udeae\ud902\udc73", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--52-dwx47758j.xn--kd3hk431k", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-.-\u251c\uda1a\udda3", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-.xn----ukp70432h", + "output": null + }, + { + "comment": "V7", + "input": "\u03c2\uff0e\ufdc1\ud83d\udf9b\u2488", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\u03c2.\u0641\u0645\u064a\ud83d\udf9b1.", + "output": "xn--3xa.xn--1-gocmu97674d." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u03a3.\u0641\u0645\u064a\ud83d\udf9b1.", + "output": "xn--4xa.xn--1-gocmu97674d." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u03c3.\u0641\u0645\u064a\ud83d\udf9b1.", + "output": "xn--4xa.xn--1-gocmu97674d." + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--4xa.xn--1-gocmu97674d.", + "output": "xn--4xa.xn--1-gocmu97674d." + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--3xa.xn--1-gocmu97674d.", + "output": "xn--3xa.xn--1-gocmu97674d." + }, + { + "comment": "V7", + "input": "\u03a3\uff0e\ufdc1\ud83d\udf9b\u2488", + "output": null + }, + { + "comment": "V7", + "input": "\u03c3\uff0e\ufdc1\ud83d\udf9b\u2488", + "output": null + }, + { + "comment": "V7", + "input": "xn--4xa.xn--dhbip2802atb20c", + "output": null + }, + { + "comment": "V7", + "input": "xn--3xa.xn--dhbip2802atb20c", + "output": null + }, + { + "comment": "V7", + "input": "9\udb40\udde5\uff0e\udb6b\udd34\u1893", + "output": null + }, + { + "comment": "V7", + "input": "9\udb40\udde5.\udb6b\udd34\u1893", + "output": null + }, + { + "comment": "V7", + "input": "9.xn--dbf91222q", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ufe12\u10b6\u0366\uff0e\u200c", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "\u3002\u10b6\u0366.\u200c", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "\u3002\u2d16\u0366.\u200c", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--hva754s.", + "output": ".xn--hva754s." + }, + { + "comment": "C1; A4_2 (ignored)", + "input": ".xn--hva754s.xn--0ug", + "output": null + }, + { + "comment": "C1; V7", + "input": "\ufe12\u2d16\u0366\uff0e\u200c", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--hva754sy94k.", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--hva754sy94k.xn--0ug", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--hva929d.", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": ".xn--hva929d.xn--0ug", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--hva929dl29p.", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--hva929dl29p.xn--0ug", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--hva754s.", + "output": "xn--hva754s." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u2d16\u0366.", + "output": "xn--hva754s." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u10b6\u0366.", + "output": "xn--hva754s." + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--hva929d.", + "output": null + }, + { + "input": "xn--hzb.xn--ukj4430l", + "output": "xn--hzb.xn--ukj4430l" + }, + { + "input": "\u08bb.\u2d03\ud838\udc12", + "output": "xn--hzb.xn--ukj4430l" + }, + { + "input": "\u08bb.\u10a3\ud838\udc12", + "output": "xn--hzb.xn--ukj4430l" + }, + { + "comment": "V7", + "input": "xn--hzb.xn--bnd2938u", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200d\u200c\u3002\uff12\u4af7\udb42\uddf7", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "\u200d\u200c\u30022\u4af7\udb42\uddf7", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--2-me5ay1273i", + "output": null + }, + { + "comment": "C1; C2; V7", + "input": "xn--0ugb.xn--2-me5ay1273i", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-\ud838\udc24\udb32\udc10\u3002\ud9e2\udf16", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----rq4re4997d.xn--l707b", + "output": null + }, + { + "comment": "C1; V7", + "input": "\udb8d\udec2\ufe12\u200c\u37c0\uff0e\u0624\u2488", + "output": null + }, + { + "comment": "C1; V7", + "input": "\udb8d\udec2\ufe12\u200c\u37c0\uff0e\u0648\u0654\u2488", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--z272f.xn--etl.xn--1-smc.", + "output": null + }, + { + "comment": "V7", + "input": "xn--etlt457ccrq7h.xn--jgb476m", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug754gxl4ldlt0k.xn--jgb476m", + "output": null + }, + { + "comment": "V7", + "input": "\u07fc\ud803\ude06.\ud80d\udd8f\ufe12\ud8ea\ude29\u10b0", + "output": null + }, + { + "comment": "V7", + "input": "\u07fc\ud803\ude06.\ud80d\udd8f\u3002\ud8ea\ude29\u10b0", + "output": null + }, + { + "comment": "V7", + "input": "\u07fc\ud803\ude06.\ud80d\udd8f\u3002\ud8ea\ude29\u2d10", + "output": null + }, + { + "comment": "V7", + "input": "xn--0tb8725k.xn--tu8d.xn--7kj73887a", + "output": null + }, + { + "comment": "V7", + "input": "\u07fc\ud803\ude06.\ud80d\udd8f\ufe12\ud8ea\ude29\u2d10", + "output": null + }, + { + "comment": "V7", + "input": "xn--0tb8725k.xn--7kj9008dt18a7py9c", + "output": null + }, + { + "comment": "V7", + "input": "xn--0tb8725k.xn--tu8d.xn--ond97931d", + "output": null + }, + { + "comment": "V7", + "input": "xn--0tb8725k.xn--ond3562jt18a7py9c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u10c5\u26ad\udb41\uddab\u22c3\uff61\ud804\udf3c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u10c5\u26ad\udb41\uddab\u22c3\u3002\ud804\udf3c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u2d25\u26ad\udb41\uddab\u22c3\u3002\ud804\udf3c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--vfh16m67gx1162b.xn--ro1d", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u2d25\u26ad\udb41\uddab\u22c3\uff61\ud804\udf3c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--9nd623g4zc5z060c.xn--ro1d", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "\udb40\udd93\u26cf-\u3002\ua852", + "output": "xn----o9p.xn--rc9a" + }, + { + "comment": "V3 (ignored)", + "input": "xn----o9p.xn--rc9a", + "output": "xn----o9p.xn--rc9a" + }, + { + "comment": "V7; V3 (ignored)", + "input": "\ud8c8\ude26\u5e37\uff61\u226f\u843a\u1dc8-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\ud8c8\ude26\u5e37\uff61>\u0338\u843a\u1dc8-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\ud8c8\ude26\u5e37\u3002\u226f\u843a\u1dc8-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "\ud8c8\ude26\u5e37\u3002>\u0338\u843a\u1dc8-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--qutw175s.xn----mimu6tf67j", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u650c\uabed\u3002\u1896-\u10b8", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u650c\uabed\u3002\u1896-\u2d18", + "output": null + }, + { + "input": "xn--p9ut19m.xn----mck373i", + "output": "xn--p9ut19m.xn----mck373i" + }, + { + "input": "\u650c\uabed.\u1896-\u2d18", + "output": "xn--p9ut19m.xn----mck373i" + }, + { + "input": "\u650c\uabed.\u1896-\u10b8", + "output": "xn--p9ut19m.xn----mck373i" + }, + { + "comment": "C2", + "input": "xn--1ug592ykp6b.xn----mck373i", + "output": null + }, + { + "comment": "V7", + "input": "xn--p9ut19m.xn----k1g451d", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug592ykp6b.xn----k1g451d", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\ua5a8\uff0e\u2497\uff13\ud212\u06f3", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\ua5a8\uff0e\u2497\uff13\u1110\u116d\u11a9\u06f3", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\ua5a8.16.3\ud212\u06f3", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\ua5a8.16.3\u1110\u116d\u11a9\u06f3", + "output": null + }, + { + "input": "xn--9r8a.16.xn--3-nyc0117m", + "output": "xn--9r8a.16.xn--3-nyc0117m" + }, + { + "input": "\ua5a8.16.3\ud212\u06f3", + "output": "xn--9r8a.16.xn--3-nyc0117m" + }, + { + "input": "\ua5a8.16.3\u1110\u116d\u11a9\u06f3", + "output": "xn--9r8a.16.xn--3-nyc0117m" + }, + { + "comment": "C1", + "input": "xn--0ug2473c.16.xn--3-nyc0117m", + "output": null + }, + { + "comment": "V7", + "input": "xn--9r8a.xn--3-nyc678tu07m", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug2473c.xn--3-nyc678tu07m", + "output": null + }, + { + "comment": "C2", + "input": "\ud835\udfcf\ud836\ude19\u2e16.\u200d", + "output": null + }, + { + "comment": "C2", + "input": "1\ud836\ude19\u2e16.\u200d", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--1-5bt6845n.", + "output": "xn--1-5bt6845n." + }, + { + "comment": "A4_2 (ignored)", + "input": "1\ud836\ude19\u2e16.", + "output": "xn--1-5bt6845n." + }, + { + "comment": "C2", + "input": "xn--1-5bt6845n.xn--1ug", + "output": null + }, + { + "comment": "V7", + "input": "F\udb40\udd5f\uff61\ud9fd\uddc5\u265a", + "output": null + }, + { + "comment": "V7", + "input": "F\udb40\udd5f\u3002\ud9fd\uddc5\u265a", + "output": null + }, + { + "comment": "V7", + "input": "f\udb40\udd5f\u3002\ud9fd\uddc5\u265a", + "output": null + }, + { + "comment": "V7", + "input": "f.xn--45hz6953f", + "output": null + }, + { + "comment": "V7", + "input": "f\udb40\udd5f\uff61\ud9fd\uddc5\u265a", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0b4d\ud804\udd34\u1de9\u3002\ud835\udfee\u10b8\ud838\udc28\ud8ce\udd47", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0b4d\ud804\udd34\u1de9\u30022\u10b8\ud838\udc28\ud8ce\udd47", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0b4d\ud804\udd34\u1de9\u30022\u2d18\ud838\udc28\ud8ce\udd47", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--9ic246gs21p.xn--2-nws2918ndrjr", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u0b4d\ud804\udd34\u1de9\u3002\ud835\udfee\u2d18\ud838\udc28\ud8ce\udd47", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--9ic246gs21p.xn--2-k1g43076adrwq", + "output": null + }, + { + "comment": "C1; V7", + "input": "\uda0e\udc2d\u200c\u200c\u2488\u3002\u52c9\ud804\udc45", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": "\uda0e\udc2d\u200c\u200c1.\u3002\u52c9\ud804\udc45", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--1-yi00h..xn--4grs325b", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": "xn--1-rgna61159u..xn--4grs325b", + "output": null + }, + { + "comment": "V7", + "input": "xn--tsh11906f.xn--4grs325b", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0uga855aez302a.xn--4grs325b", + "output": null + }, + { + "comment": "V7", + "input": "\u1843.\u73bf\ud96c\ude1c\udb15\udf90", + "output": null + }, + { + "comment": "V7", + "input": "xn--27e.xn--7cy81125a0yq4a", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\u200c\uff61\u2488\u226f\ud835\udff5", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\u200c\uff61\u2488>\u0338\ud835\udff5", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u200c\u30021.\u226f9", + "output": null + }, + { + "comment": "C1", + "input": "\u200c\u200c\u30021.>\u03389", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".1.xn--9-ogo", + "output": ".1.xn--9-ogo" + }, + { + "comment": "C1", + "input": "xn--0uga.1.xn--9-ogo", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--9-ogo37g", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0uga.xn--9-ogo37g", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u20da\uff0e\ud805\ude3f-", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u20da.\ud805\ude3f-", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--w0g.xn----bd0j", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u1082-\u200d\ua8ea\uff0e\ua84a\u200d\ud9b3\ude33", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\u1082-\u200d\ua8ea.\ua84a\u200d\ud9b3\ude33", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn----gyg3618i.xn--jc9ao4185a", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn----gyg250jio7k.xn--1ug8774cri56d", + "output": null + }, + { + "comment": "V6", + "input": "\ud804\ude35\u5eca.\ud802\udc0d", + "output": null + }, + { + "comment": "V6", + "input": "xn--xytw701b.xn--yc9c", + "output": null + }, + { + "comment": "V7", + "input": "\u10be\ud899\udec0\ud82d\uddfb\uff0e\u1897\ub9ab", + "output": null + }, + { + "comment": "V7", + "input": "\u10be\ud899\udec0\ud82d\uddfb\uff0e\u1897\u1105\u1174\u11c2", + "output": null + }, + { + "comment": "V7", + "input": "\u10be\ud899\udec0\ud82d\uddfb.\u1897\ub9ab", + "output": null + }, + { + "comment": "V7", + "input": "\u10be\ud899\udec0\ud82d\uddfb.\u1897\u1105\u1174\u11c2", + "output": null + }, + { + "comment": "V7", + "input": "\u2d1e\ud899\udec0\ud82d\uddfb.\u1897\u1105\u1174\u11c2", + "output": null + }, + { + "comment": "V7", + "input": "\u2d1e\ud899\udec0\ud82d\uddfb.\u1897\ub9ab", + "output": null + }, + { + "comment": "V7", + "input": "xn--mlj0486jgl2j.xn--hbf6853f", + "output": null + }, + { + "comment": "V7", + "input": "\u2d1e\ud899\udec0\ud82d\uddfb\uff0e\u1897\u1105\u1174\u11c2", + "output": null + }, + { + "comment": "V7", + "input": "\u2d1e\ud899\udec0\ud82d\uddfb\uff0e\u1897\ub9ab", + "output": null + }, + { + "comment": "V7", + "input": "xn--2nd8876sgl2j.xn--hbf6853f", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u00df\u200d\u103a\uff61\u2488", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--ss-f4j.b.", + "output": "xn--ss-f4j.b." + }, + { + "comment": "A4_2 (ignored)", + "input": "ss\u103a.b.", + "output": "xn--ss-f4j.b." + }, + { + "comment": "A4_2 (ignored)", + "input": "SS\u103a.B.", + "output": "xn--ss-f4j.b." + }, + { + "comment": "A4_2 (ignored)", + "input": "Ss\u103a.b.", + "output": "xn--ss-f4j.b." + }, + { + "comment": "C2; A4_2 (ignored)", + "input": "xn--ss-f4j585j.b.", + "output": null + }, + { + "comment": "C2; A4_2 (ignored)", + "input": "xn--zca679eh2l.b.", + "output": null + }, + { + "comment": "C2; V7", + "input": "SS\u200d\u103a\uff61\u2488", + "output": null + }, + { + "comment": "C2; V7", + "input": "ss\u200d\u103a\uff61\u2488", + "output": null + }, + { + "comment": "C2; V7", + "input": "Ss\u200d\u103a\uff61\u2488", + "output": null + }, + { + "comment": "V7", + "input": "xn--ss-f4j.xn--tsh", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--ss-f4j585j.xn--tsh", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--zca679eh2l.xn--tsh", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "SS\u103a.b.", + "output": "xn--ss-f4j.b." + }, + { + "input": "\u06cc\ud802\ude3f\uff0e\u00df\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--zca216edt0r" + }, + { + "input": "\u06cc\ud802\ude3f.\u00df\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--zca216edt0r" + }, + { + "input": "\u06cc\ud802\ude3f.SS\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "input": "\u06cc\ud802\ude3f.ss\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "input": "xn--clb2593k.xn--ss-toj6092t", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "input": "xn--clb2593k.xn--zca216edt0r", + "output": "xn--clb2593k.xn--zca216edt0r" + }, + { + "input": "\u06cc\ud802\ude3f\uff0eSS\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "input": "\u06cc\ud802\ude3f\uff0ess\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "input": "\u06cc\ud802\ude3f.Ss\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "input": "\u06cc\ud802\ude3f\uff0eSs\u0f84\ud804\udf6c", + "output": "xn--clb2593k.xn--ss-toj6092t" + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "\ud835\udfe0\u226e\u200c\uff61\udb40\udd71\u17b4", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "\ud835\udfe0<\u0338\u200c\uff61\udb40\udd71\u17b4", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "8\u226e\u200c\u3002\udb40\udd71\u17b4", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "8<\u0338\u200c\u3002\udb40\udd71\u17b4", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--8-ngo.", + "output": "xn--8-ngo." + }, + { + "comment": "A4_2 (ignored)", + "input": "8\u226e.", + "output": "xn--8-ngo." + }, + { + "comment": "A4_2 (ignored)", + "input": "8<\u0338.", + "output": "xn--8-ngo." + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "xn--8-sgn10i.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--8-ngo.xn--z3e", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--8-sgn10i.xn--z3e", + "output": null + }, + { + "comment": "V7", + "input": "\u1895\u226f\ufe12\ud8d0\udcaf\uff0e\u10a0", + "output": null + }, + { + "comment": "V7", + "input": "\u1895>\u0338\ufe12\ud8d0\udcaf\uff0e\u10a0", + "output": null + }, + { + "comment": "V7", + "input": "\u1895\u226f\u3002\ud8d0\udcaf.\u10a0", + "output": null + }, + { + "comment": "V7", + "input": "\u1895>\u0338\u3002\ud8d0\udcaf.\u10a0", + "output": null + }, + { + "comment": "V7", + "input": "\u1895>\u0338\u3002\ud8d0\udcaf.\u2d00", + "output": null + }, + { + "comment": "V7", + "input": "\u1895\u226f\u3002\ud8d0\udcaf.\u2d00", + "output": null + }, + { + "comment": "V7", + "input": "xn--fbf851c.xn--ko1u.xn--rkj", + "output": null + }, + { + "comment": "V7", + "input": "\u1895>\u0338\ufe12\ud8d0\udcaf\uff0e\u2d00", + "output": null + }, + { + "comment": "V7", + "input": "\u1895\u226f\ufe12\ud8d0\udcaf\uff0e\u2d00", + "output": null + }, + { + "comment": "V7", + "input": "xn--fbf851cq98poxw1a.xn--rkj", + "output": null + }, + { + "comment": "V7", + "input": "xn--fbf851c.xn--ko1u.xn--7md", + "output": null + }, + { + "comment": "V7", + "input": "xn--fbf851cq98poxw1a.xn--7md", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u0f9f\uff0e-\u082a", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u0f9f.-\u082a", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--vfd.xn----fhd", + "output": null + }, + { + "comment": "V7", + "input": "\u1d6c\udb40\udda0\uff0e\ud552\u2492\u2488\udbe0\udd26", + "output": null + }, + { + "comment": "V7", + "input": "\u1d6c\udb40\udda0\uff0e\u1111\u1175\u11bd\u2492\u2488\udbe0\udd26", + "output": null + }, + { + "comment": "V7", + "input": "\u1d6c\udb40\udda0.\ud55211.1.\udbe0\udd26", + "output": null + }, + { + "comment": "V7", + "input": "\u1d6c\udb40\udda0.\u1111\u1175\u11bd11.1.\udbe0\udd26", + "output": null + }, + { + "comment": "V7", + "input": "xn--tbg.xn--11-5o7k.1.xn--k469f", + "output": null + }, + { + "comment": "V7", + "input": "xn--tbg.xn--tsht7586kyts9l", + "output": null + }, + { + "comment": "V7", + "input": "\u2488\u270c\uda3e\udf1f\uff0e\ud835\udfe1\ud943\udc63", + "output": null + }, + { + "comment": "V7", + "input": "1.\u270c\uda3e\udf1f.9\ud943\udc63", + "output": null + }, + { + "comment": "V7", + "input": "1.xn--7bi44996f.xn--9-o706d", + "output": null + }, + { + "comment": "V7", + "input": "xn--tsh24g49550b.xn--9-o706d", + "output": null + }, + { + "comment": "V6", + "input": "\u03c2\uff0e\ua9c0\ua8c4", + "output": null + }, + { + "comment": "V6", + "input": "\u03c2.\ua9c0\ua8c4", + "output": null + }, + { + "comment": "V6", + "input": "\u03a3.\ua9c0\ua8c4", + "output": null + }, + { + "comment": "V6", + "input": "\u03c3.\ua9c0\ua8c4", + "output": null + }, + { + "comment": "V6", + "input": "xn--4xa.xn--0f9ars", + "output": null + }, + { + "comment": "V6", + "input": "xn--3xa.xn--0f9ars", + "output": null + }, + { + "comment": "V6", + "input": "\u03a3\uff0e\ua9c0\ua8c4", + "output": null + }, + { + "comment": "V6", + "input": "\u03c3\uff0e\ua9c0\ua8c4", + "output": null + }, + { + "input": "\u7f9a\uff61\u226f", + "output": "xn--xt0a.xn--hdh" + }, + { + "input": "\u7f9a\uff61>\u0338", + "output": "xn--xt0a.xn--hdh" + }, + { + "input": "\u7f9a\u3002\u226f", + "output": "xn--xt0a.xn--hdh" + }, + { + "input": "\u7f9a\u3002>\u0338", + "output": "xn--xt0a.xn--hdh" + }, + { + "input": "xn--xt0a.xn--hdh", + "output": "xn--xt0a.xn--hdh" + }, + { + "input": "\u7f9a.\u226f", + "output": "xn--xt0a.xn--hdh" + }, + { + "input": "\u7f9a.>\u0338", + "output": "xn--xt0a.xn--hdh" + }, + { + "comment": "V7", + "input": "\u2786\ud99e\uddd5\u1ed7\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb", + "output": null + }, + { + "comment": "V7", + "input": "\u2786\ud99e\uddd5o\u0302\u0303\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2786\ud99e\uddd5\u1ed71..\uda06\udf12\ud945\ude2e\u085b9", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2786\ud99e\uddd5o\u0302\u03031..\uda06\udf12\ud945\ude2e\u085b9", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2786\ud99e\uddd5O\u0302\u03031..\uda06\udf12\ud945\ude2e\u085b9", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u2786\ud99e\uddd5\u1ed61..\uda06\udf12\ud945\ude2e\u085b9", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--1-3xm292b6044r..xn--9-6jd87310jtcqs", + "output": null + }, + { + "comment": "V7", + "input": "\u2786\ud99e\uddd5O\u0302\u0303\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb", + "output": null + }, + { + "comment": "V7", + "input": "\u2786\ud99e\uddd5\u1ed6\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb", + "output": null + }, + { + "comment": "V7", + "input": "xn--6lg26tvvc6v99z.xn--9-6jd87310jtcqs", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--ye6h", + "output": ".xn--ye6h" + }, + { + "input": "xn--ye6h", + "output": "xn--ye6h" + }, + { + "input": "\ud83a\udd3a", + "output": "xn--ye6h" + }, + { + "input": "\ud83a\udd18", + "output": "xn--ye6h" + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "\u073c\u200c-\u3002\ud80d\udc3e\u00df", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "\u073c\u200c-\u3002\ud80d\udc3eSS", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "\u073c\u200c-\u3002\ud80d\udc3ess", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "\u073c\u200c-\u3002\ud80d\udc3eSs", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----s2c.xn--ss-066q", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "xn----s2c071q.xn--ss-066q", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "xn----s2c071q.xn--zca7848m", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "-\uda9d\udf6c\u135e\ud805\udf27.\u1deb-\ufe12", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored); A4_2 (ignored)", + "input": "-\uda9d\udf6c\u135e\ud805\udf27.\u1deb-\u3002", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored); A4_2 (ignored)", + "input": "xn----b5h1837n2ok9f.xn----mkm.", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----b5h1837n2ok9f.xn----mkmw278h", + "output": null + }, + { + "comment": "V7", + "input": "\ufe12.\uda2a\udc21\u1a59", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u3002.\uda2a\udc21\u1a59", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "..xn--cof61594i", + "output": null + }, + { + "comment": "V7", + "input": "xn--y86c.xn--cof61594i", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "\ud807\udc3a.-\uda05\udfcf", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--jk3d.xn----iz68g", + "output": null + }, + { + "comment": "V7", + "input": "\udb43\udee9\uff0e\u8d4f", + "output": null + }, + { + "comment": "V7", + "input": "\udb43\udee9.\u8d4f", + "output": null + }, + { + "comment": "V7", + "input": "xn--2856e.xn--6o3a", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u10ad\uff0e\ud8f4\udde6\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u10ad.\ud8f4\udde6\u200c", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u2d0d.\ud8f4\udde6\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--4kj.xn--p01x", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--4kj.xn--0ug56448b", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u2d0d\uff0e\ud8f4\udde6\u200c", + "output": null + }, + { + "comment": "V7", + "input": "xn--lnd.xn--p01x", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--lnd.xn--0ug56448b", + "output": null + }, + { + "input": "\ud835\udfdb\uff0e\uf9f8", + "output": "3.xn--6vz" + }, + { + "input": "\ud835\udfdb\uff0e\u7b20", + "output": "3.xn--6vz" + }, + { + "input": "3.\u7b20", + "output": "3.xn--6vz" + }, + { + "input": "3.xn--6vz", + "output": "3.xn--6vz" + }, + { + "comment": "C2; V3 (ignored)", + "input": "-\u200d.\u10be\ud800\udef7", + "output": null + }, + { + "comment": "C2; V3 (ignored)", + "input": "-\u200d.\u2d1e\ud800\udef7", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "-.xn--mlj8559d", + "output": "-.xn--mlj8559d" + }, + { + "comment": "C2; V3 (ignored)", + "input": "xn----ugn.xn--mlj8559d", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-.xn--2nd2315j", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn----ugn.xn--2nd2315j", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03c2\u00df\u0731\uff0e\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03c2\u00df\u0731.\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03a3SS\u0731.\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03c3ss\u0731.\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03a3ss\u0731.\u0bcd", + "output": null + }, + { + "comment": "V6", + "input": "xn--ss-ubc826a.xn--xmc", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--ss-ubc826ab34b.xn--xmc", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03a3\u00df\u0731.\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03c3\u00df\u0731.\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--zca39lk1di19a.xn--xmc", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--zca19ln1di19a.xn--xmc", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03a3SS\u0731\uff0e\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03c3ss\u0731\uff0e\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03a3ss\u0731\uff0e\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03a3\u00df\u0731\uff0e\u0bcd", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u200d\u03c3\u00df\u0731\uff0e\u0bcd", + "output": null + }, + { + "comment": "C2", + "input": "\u2260\uff0e\u200d", + "output": null + }, + { + "comment": "C2", + "input": "=\u0338\uff0e\u200d", + "output": null + }, + { + "comment": "C2", + "input": "\u2260.\u200d", + "output": null + }, + { + "comment": "C2", + "input": "=\u0338.\u200d", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--1ch.", + "output": "xn--1ch." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u2260.", + "output": "xn--1ch." + }, + { + "comment": "A4_2 (ignored)", + "input": "=\u0338.", + "output": "xn--1ch." + }, + { + "comment": "C2", + "input": "xn--1ch.xn--1ug", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03c2\uff0e\u03c2\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03c2.\u03c2\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03a3.\u03a3\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03c3.\u03c2\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03c3.\u03c3\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03a3.\u03c3\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "xn--4xa502av8297a.xn--4xa6055k", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03a3.\u03c2\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "xn--4xa502av8297a.xn--3xa8055k", + "output": null + }, + { + "comment": "V7", + "input": "xn--3xa702av8297a.xn--3xa8055k", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03a3\uff0e\u03a3\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03c3\uff0e\u03c2\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03c3\uff0e\u03c3\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03a3\uff0e\u03c3\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\udb5c\udef5\u09cd\u03a3\uff0e\u03c2\ud802\ude3f", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udd12\uff61\ub967", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udd12\uff61\u1105\u1172\u11b6", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udd12\u3002\ub967", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udd12\u3002\u1105\u1172\u11b6", + "output": null + }, + { + "comment": "V7", + "input": "xn--s264a.xn--pw2b", + "output": null + }, + { + "comment": "V7", + "input": "\u1846\ud805\udcdd\uff0e\ud83b\udd46", + "output": null + }, + { + "comment": "V7", + "input": "\u1846\ud805\udcdd.\ud83b\udd46", + "output": null + }, + { + "comment": "V7", + "input": "xn--57e0440k.xn--k86h", + "output": null + }, + { + "comment": "V7", + "input": "\udbef\udfe6\uff61\u183d", + "output": null + }, + { + "comment": "V7", + "input": "\udbef\udfe6\u3002\u183d", + "output": null + }, + { + "comment": "V7", + "input": "xn--j890g.xn--w7e", + "output": null + }, + { + "comment": "C2", + "input": "\u5b03\ud834\udf4c\uff0e\u200d\u0b44", + "output": null + }, + { + "comment": "C2", + "input": "\u5b03\ud834\udf4c.\u200d\u0b44", + "output": null + }, + { + "comment": "V6", + "input": "xn--b6s0078f.xn--0ic", + "output": null + }, + { + "comment": "C2", + "input": "xn--b6s0078f.xn--0ic557h", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c.\ud93d\udee4", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--q823a", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug.xn--q823a", + "output": null + }, + { + "comment": "V7", + "input": "\udaa9\uded5\u10a3\u4805\uff0e\ud803\ude11", + "output": null + }, + { + "comment": "V7", + "input": "\udaa9\uded5\u10a3\u4805.\ud803\ude11", + "output": null + }, + { + "comment": "V7", + "input": "\udaa9\uded5\u2d03\u4805.\ud803\ude11", + "output": null + }, + { + "comment": "V7", + "input": "xn--ukju77frl47r.xn--yl0d", + "output": null + }, + { + "comment": "V7", + "input": "\udaa9\uded5\u2d03\u4805\uff0e\ud803\ude11", + "output": null + }, + { + "comment": "V7", + "input": "xn--bnd074zr557n.xn--yl0d", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "-\uff61\ufe12", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "-\u3002\u3002", + "output": "-.." + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "-..", + "output": "-.." + }, + { + "comment": "V7; V3 (ignored)", + "input": "-.xn--y86c", + "output": null + }, + { + "comment": "C2", + "input": "\u200d.F", + "output": null + }, + { + "comment": "C2", + "input": "\u200d.f", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".f", + "output": ".f" + }, + { + "comment": "C2", + "input": "xn--1ug.f", + "output": null + }, + { + "input": "f", + "output": "f" + }, + { + "comment": "C2", + "input": "\u200d\u3a32\uff61\u00df", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\u3002\u00df", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\u3002SS", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\u3002ss", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\u3002Ss", + "output": null + }, + { + "input": "xn--9bm.ss", + "output": "xn--9bm.ss" + }, + { + "input": "\u3a32.ss", + "output": "xn--9bm.ss" + }, + { + "input": "\u3a32.SS", + "output": "xn--9bm.ss" + }, + { + "input": "\u3a32.Ss", + "output": "xn--9bm.ss" + }, + { + "comment": "C2", + "input": "xn--1ug914h.ss", + "output": null + }, + { + "comment": "C2", + "input": "xn--1ug914h.xn--zca", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\uff61SS", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\uff61ss", + "output": null + }, + { + "comment": "C2", + "input": "\u200d\u3a32\uff61Ss", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\uff0e\udbc3\ude28", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d.\udbc3\ude28", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--h327f", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1ug.xn--h327f", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udf7b\ud8f2\udd41\uff61\u2260\ud835\udff2", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udf7b\ud8f2\udd41\uff61=\u0338\ud835\udff2", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udf7b\ud8f2\udd41\u3002\u22606", + "output": null + }, + { + "comment": "V7", + "input": "\ud94e\udf7b\ud8f2\udd41\u3002=\u03386", + "output": null + }, + { + "comment": "V7", + "input": "xn--h79w4z99a.xn--6-tfo", + "output": null + }, + { + "comment": "V7", + "input": "xn--98e.xn--om9c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\uaaf6\u188f\u0e3a\uff12.\ud800\udee2\u0745\u0f9f\ufe12", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "\uaaf6\u188f\u0e3a2.\ud800\udee2\u0745\u0f9f\u3002", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "xn--2-2zf840fk16m.xn--sob093b2m7s.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--2-2zf840fk16m.xn--sob093bj62sz9d", + "output": null + }, + { + "comment": "V7", + "input": "\udad7\udd27\uff61\u2260-\udb41\ude44\u2f9b", + "output": null + }, + { + "comment": "V7", + "input": "\udad7\udd27\uff61=\u0338-\udb41\ude44\u2f9b", + "output": null + }, + { + "comment": "V7", + "input": "\udad7\udd27\u3002\u2260-\udb41\ude44\u8d70", + "output": null + }, + { + "comment": "V7", + "input": "\udad7\udd27\u3002=\u0338-\udb41\ude44\u8d70", + "output": null + }, + { + "comment": "V7", + "input": "xn--gm57d.xn----tfo4949b3664m", + "output": null + }, + { + "input": "\ud835\udfce\u3002\u752f", + "output": "0.xn--qny" + }, + { + "input": "0\u3002\u752f", + "output": "0.xn--qny" + }, + { + "input": "0.xn--qny", + "output": "0.xn--qny" + }, + { + "input": "0.\u752f", + "output": "0.xn--qny" + }, + { + "comment": "V6; V3 (ignored)", + "input": "-\u2f86\uff0e\uaaf6", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "-\u820c.\uaaf6", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn----ef8c.xn--2v9a", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "-\uff61\u1898", + "output": "-.xn--ibf" + }, + { + "comment": "V3 (ignored)", + "input": "-\u3002\u1898", + "output": "-.xn--ibf" + }, + { + "comment": "V3 (ignored)", + "input": "-.xn--ibf", + "output": "-.xn--ibf" + }, + { + "input": "\ud83c\udcb4\u10ab.\u226e", + "output": "xn--2kj7565l.xn--gdh" + }, + { + "input": "\ud83c\udcb4\u10ab.<\u0338", + "output": "xn--2kj7565l.xn--gdh" + }, + { + "input": "\ud83c\udcb4\u2d0b.<\u0338", + "output": "xn--2kj7565l.xn--gdh" + }, + { + "input": "\ud83c\udcb4\u2d0b.\u226e", + "output": "xn--2kj7565l.xn--gdh" + }, + { + "input": "xn--2kj7565l.xn--gdh", + "output": "xn--2kj7565l.xn--gdh" + }, + { + "comment": "V7", + "input": "xn--jnd1986v.xn--gdh", + "output": null + }, + { + "comment": "C1", + "input": "\u74bc\ud836\ude2d\uff61\u200c\udb40\udddf", + "output": null + }, + { + "comment": "C1", + "input": "\u74bc\ud836\ude2d\u3002\u200c\udb40\udddf", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--gky8837e.", + "output": "xn--gky8837e." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u74bc\ud836\ude2d.", + "output": "xn--gky8837e." + }, + { + "comment": "C1", + "input": "xn--gky8837e.xn--0ug", + "output": null + }, + { + "comment": "C1", + "input": "\u200c.\u200c", + "output": null + }, + { + "comment": "C1", + "input": "xn--0ug.xn--0ug", + "output": null + }, + { + "input": "xn--157b.xn--gnb", + "output": "xn--157b.xn--gnb" + }, + { + "input": "\ud29b.\u0716", + "output": "xn--157b.xn--gnb" + }, + { + "input": "\u1110\u1171\u11c2.\u0716", + "output": "xn--157b.xn--gnb" + }, + { + "comment": "V6; V7", + "input": "\u10b7\uff0e\u05c2\ud804\udd34\ua9b7\ud920\udce8", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u10b7\uff0e\ud804\udd34\u05c2\ua9b7\ud920\udce8", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u10b7.\ud804\udd34\u05c2\ua9b7\ud920\udce8", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u2d17.\ud804\udd34\u05c2\ua9b7\ud920\udce8", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--flj.xn--qdb0605f14ycrms3c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u2d17\uff0e\ud804\udd34\u05c2\ua9b7\ud920\udce8", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u2d17\uff0e\u05c2\ud804\udd34\ua9b7\ud920\udce8", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--vnd.xn--qdb0605f14ycrms3c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u2488\u916b\ufe12\u3002\u08d6", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "1.\u916b\u3002\u3002\u08d6", + "output": null + }, + { + "comment": "V6; A4_2 (ignored)", + "input": "1.xn--8j4a..xn--8zb", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--tsh4490bfe8c.xn--8zb", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u2de3\u200c\u226e\u1a6b.\u200c\u0e3a", + "output": null + }, + { + "comment": "C1; V6", + "input": "\u2de3\u200c<\u0338\u1a6b.\u200c\u0e3a", + "output": null + }, + { + "comment": "V6", + "input": "xn--uof548an0j.xn--o4c", + "output": null + }, + { + "comment": "C1; V6", + "input": "xn--uof63xk4bf3s.xn--o4c732g", + "output": null + }, + { + "comment": "V7", + "input": "xn--co6h.xn--1-kwssa", + "output": null + }, + { + "comment": "V7", + "input": "xn--co6h.xn--1-h1g429s", + "output": null + }, + { + "comment": "V7", + "input": "xn--co6h.xn--1-h1gs", + "output": null + }, + { + "comment": "V6; V7", + "input": "\ua806\u3002\ud8ad\ude8f\u0fb0\u2495", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": "\ua806\u3002\ud8ad\ude8f\u0fb014.", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": "xn--l98a.xn--14-jsj57880f.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--l98a.xn--dgd218hhp28d", + "output": null + }, + { + "comment": "C2", + "input": "\ud835\udfe04\udb40\uddd7\ud834\ude3b\uff0e\u200d\ud800\udef5\u26e7\u200d", + "output": null + }, + { + "comment": "C2", + "input": "84\udb40\uddd7\ud834\ude3b.\u200d\ud800\udef5\u26e7\u200d", + "output": null + }, + { + "input": "xn--84-s850a.xn--59h6326e", + "output": "xn--84-s850a.xn--59h6326e" + }, + { + "input": "84\ud834\ude3b.\ud800\udef5\u26e7", + "output": "xn--84-s850a.xn--59h6326e" + }, + { + "comment": "C2", + "input": "xn--84-s850a.xn--1uga573cfq1w", + "output": null + }, + { + "input": "\u226e\ud835\udfd5\uff0e\u8b16\u00df\u226f", + "output": "xn--7-mgo.xn--zca892oly5e" + }, + { + "input": "<\u0338\ud835\udfd5\uff0e\u8b16\u00df>\u0338", + "output": "xn--7-mgo.xn--zca892oly5e" + }, + { + "input": "\u226e7.\u8b16\u00df\u226f", + "output": "xn--7-mgo.xn--zca892oly5e" + }, + { + "input": "<\u03387.\u8b16\u00df>\u0338", + "output": "xn--7-mgo.xn--zca892oly5e" + }, + { + "input": "<\u03387.\u8b16SS>\u0338", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "\u226e7.\u8b16SS\u226f", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "\u226e7.\u8b16ss\u226f", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "<\u03387.\u8b16ss>\u0338", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "<\u03387.\u8b16Ss>\u0338", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "\u226e7.\u8b16Ss\u226f", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "xn--7-mgo.xn--ss-xjvv174c", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "xn--7-mgo.xn--zca892oly5e", + "output": "xn--7-mgo.xn--zca892oly5e" + }, + { + "input": "<\u0338\ud835\udfd5\uff0e\u8b16SS>\u0338", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "\u226e\ud835\udfd5\uff0e\u8b16SS\u226f", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "\u226e\ud835\udfd5\uff0e\u8b16ss\u226f", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "<\u0338\ud835\udfd5\uff0e\u8b16ss>\u0338", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "<\u0338\ud835\udfd5\uff0e\u8b16Ss>\u0338", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "input": "\u226e\ud835\udfd5\uff0e\u8b16Ss\u226f", + "output": "xn--7-mgo.xn--ss-xjvv174c" + }, + { + "comment": "C1; V7", + "input": "\ud975\udf0e\u2488\uff61\u200c\ud835\udfe4", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": "\ud975\udf0e1.\u3002\u200c2", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--1-ex54e..c", + "output": null + }, + { + "comment": "C1; V7; A4_2 (ignored)", + "input": "xn--1-ex54e..xn--2-rgn", + "output": null + }, + { + "comment": "V7", + "input": "xn--tsh94183d.c", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--tsh94183d.xn--2-rgn", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\uff61\u00df\ud805\udcc3", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\u3002\u00df\ud805\udcc3", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\u3002SS\ud805\udcc3", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\u3002ss\ud805\udcc3", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\u3002Ss\ud805\udcc3", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--ss-bh7o", + "output": ".xn--ss-bh7o" + }, + { + "comment": "C1; C2", + "input": "xn--0ugb.xn--ss-bh7o", + "output": null + }, + { + "comment": "C1; C2", + "input": "xn--0ugb.xn--zca0732l", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\uff61SS\ud805\udcc3", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\uff61ss\ud805\udcc3", + "output": null + }, + { + "comment": "C1; C2", + "input": "\u200d\u200c\udb40\uddaa\uff61Ss\ud805\udcc3", + "output": null + }, + { + "input": "xn--ss-bh7o", + "output": "xn--ss-bh7o" + }, + { + "input": "ss\ud805\udcc3", + "output": "xn--ss-bh7o" + }, + { + "input": "SS\ud805\udcc3", + "output": "xn--ss-bh7o" + }, + { + "input": "Ss\ud805\udcc3", + "output": "xn--ss-bh7o" + }, + { + "comment": "C1; V7", + "input": "\ufe12\u200c\u30f6\u44a9.\ua86a", + "output": null + }, + { + "comment": "C1; A4_2 (ignored)", + "input": "\u3002\u200c\u30f6\u44a9.\ua86a", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--qekw60d.xn--gd9a", + "output": ".xn--qekw60d.xn--gd9a" + }, + { + "comment": "C1; A4_2 (ignored)", + "input": ".xn--0ug287dj0o.xn--gd9a", + "output": null + }, + { + "comment": "V7", + "input": "xn--qekw60dns9k.xn--gd9a", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug287dj0or48o.xn--gd9a", + "output": null + }, + { + "input": "xn--qekw60d.xn--gd9a", + "output": "xn--qekw60d.xn--gd9a" + }, + { + "input": "\u30f6\u44a9.\ua86a", + "output": "xn--qekw60d.xn--gd9a" + }, + { + "comment": "C1; V7", + "input": "\u200c\u2488\ud852\udf8d.\udb49\udccb\u1a60", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c1.\ud852\udf8d.\udb49\udccb\u1a60", + "output": null + }, + { + "comment": "V7", + "input": "1.xn--4x6j.xn--jof45148n", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--1-rgn.xn--4x6j.xn--jof45148n", + "output": null + }, + { + "comment": "V7", + "input": "xn--tshw462r.xn--jof45148n", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ug88o7471d.xn--jof45148n", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\ud834\udd75\uff61\ud835\udfeb\ud838\udc08\u4b3a\u2488", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud834\udd75\u30029\ud838\udc08\u4b3a1.", + "output": ".xn--91-030c1650n." + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--91-030c1650n.", + "output": ".xn--91-030c1650n." + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--9-ecp936non25a", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "xn--3f1h.xn--91-030c1650n.", + "output": null + }, + { + "comment": "V7", + "input": "xn--3f1h.xn--9-ecp936non25a", + "output": null + }, + { + "input": "xn--8c1a.xn--2ib8jn539l", + "output": "xn--8c1a.xn--2ib8jn539l" + }, + { + "input": "\u821b.\u067d\ud83a\udd34\u06bb", + "output": "xn--8c1a.xn--2ib8jn539l" + }, + { + "input": "\u821b.\u067d\ud83a\udd12\u06bb", + "output": "xn--8c1a.xn--2ib8jn539l" + }, + { + "comment": "V6; V3 (ignored)", + "input": "-\udb40\udd710\uff61\u17cf\u1dfd\ud187\uc2ed", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "-\udb40\udd710\uff61\u17cf\u1dfd\u1110\u1168\u11aa\u1109\u1175\u11b8", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "-\udb40\udd710\u3002\u17cf\u1dfd\ud187\uc2ed", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "-\udb40\udd710\u3002\u17cf\u1dfd\u1110\u1168\u11aa\u1109\u1175\u11b8", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "-0.xn--r4e872ah77nghm", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u10bf\u10b5\u10e0\uff61\u0b4d", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u10bf\u10b5\u10e0\u3002\u0b4d", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u2d1f\u2d15\u10e0\u3002\u0b4d", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u10bf\u10b5\u1ca0\u3002\u0b4d", + "output": null + }, + { + "comment": "V6", + "input": "xn--1od555l3a.xn--9ic", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u2d1f\u2d15\u10e0\uff61\u0b4d", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u10bf\u10b5\u1ca0\uff61\u0b4d", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--tndt4hvw.xn--9ic", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--1od7wz74eeb.xn--9ic", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u10bf\u2d15\u10e0\u3002\u0b4d", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--3nd0etsm92g.xn--9ic", + "output": null + }, + { + "comment": "V6", + "input": "\u115f\u10bf\u2d15\u10e0\uff61\u0b4d", + "output": null + }, + { + "comment": "V7", + "input": "xn--l96h.xn--o8e4044k", + "output": null + }, + { + "comment": "V7", + "input": "xn--l96h.xn--03e93aq365d", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\ud835\udfdb\ud834\uddaa\ua8c4\uff61\ua8ea-", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\ud835\udfdb\ua8c4\ud834\uddaa\uff61\ua8ea-", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "3\ua8c4\ud834\uddaa\u3002\ua8ea-", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn--3-sl4eu679e.xn----xn4e", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1139\uff61\u0eca\uda42\udfe4\udb40\udd1e", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1139\u3002\u0eca\uda42\udfe4\udb40\udd1e", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--lrd.xn--s8c05302k", + "output": null + }, + { + "comment": "V7", + "input": "\u10a6\udaae\udca9\uff0e\udb40\udda1\ufe09\ud83a\udd0d", + "output": null + }, + { + "comment": "V7", + "input": "\u10a6\udaae\udca9.\udb40\udda1\ufe09\ud83a\udd0d", + "output": null + }, + { + "comment": "V7", + "input": "\u2d06\udaae\udca9.\udb40\udda1\ufe09\ud83a\udd2f", + "output": null + }, + { + "comment": "V7", + "input": "xn--xkjw3965g.xn--ne6h", + "output": null + }, + { + "comment": "V7", + "input": "\u2d06\udaae\udca9\uff0e\udb40\udda1\ufe09\ud83a\udd2f", + "output": null + }, + { + "comment": "V7", + "input": "xn--end82983m.xn--ne6h", + "output": null + }, + { + "comment": "V7", + "input": "\u2d06\udaae\udca9.\udb40\udda1\ufe09\ud83a\udd0d", + "output": null + }, + { + "comment": "V7", + "input": "\u2d06\udaae\udca9\uff0e\udb40\udda1\ufe09\ud83a\udd0d", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udee8.\ud9d5\udfe2\ud835\udfe8\ua8c4", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udee8.\ud9d5\udfe26\ua8c4", + "output": null + }, + { + "comment": "V7", + "input": "xn--mi60a.xn--6-sl4es8023c", + "output": null + }, + { + "comment": "V7", + "input": "\ud800\udef8\udb79\ude0b\u10c2.\u10a1", + "output": null + }, + { + "comment": "V7", + "input": "\ud800\udef8\udb79\ude0b\u2d22.\u2d01", + "output": null + }, + { + "comment": "V7", + "input": "\ud800\udef8\udb79\ude0b\u10c2.\u2d01", + "output": null + }, + { + "comment": "V7", + "input": "xn--qlj1559dr224h.xn--skj", + "output": null + }, + { + "comment": "V7", + "input": "xn--6nd5215jr2u0h.xn--skj", + "output": null + }, + { + "comment": "V7", + "input": "xn--6nd5215jr2u0h.xn--8md", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udc7f\ua806\u2084\uda65\udf86\uff61\ud88a\ude67\udb41\udcb9\u03c2", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udc7f\ua8064\uda65\udf86\u3002\ud88a\ude67\udb41\udcb9\u03c2", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udc7f\ua8064\uda65\udf86\u3002\ud88a\ude67\udb41\udcb9\u03a3", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udc7f\ua8064\uda65\udf86\u3002\ud88a\ude67\udb41\udcb9\u03c3", + "output": null + }, + { + "comment": "V7", + "input": "xn--4-w93ej7463a9io5a.xn--4xa31142bk3f0d", + "output": null + }, + { + "comment": "V7", + "input": "xn--4-w93ej7463a9io5a.xn--3xa51142bk3f0d", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udc7f\ua806\u2084\uda65\udf86\uff61\ud88a\ude67\udb41\udcb9\u03a3", + "output": null + }, + { + "comment": "V7", + "input": "\ud91d\udc7f\ua806\u2084\uda65\udf86\uff61\ud88a\ude67\udb41\udcb9\u03c3", + "output": null + }, + { + "comment": "V7", + "input": "\ud8ba\udcac\u3002\u0729\u3002\ucbd95", + "output": null + }, + { + "comment": "V7", + "input": "\ud8ba\udcac\u3002\u0729\u3002\u110d\u1173\u11ac5", + "output": null + }, + { + "comment": "V7", + "input": "xn--t92s.xn--znb.xn--5-y88f", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u17ca.\u200d\ud835\udfee\ud804\udc3f", + "output": null + }, + { + "comment": "C2; V6", + "input": "\u17ca.\u200d2\ud804\udc3f", + "output": null + }, + { + "comment": "V6", + "input": "xn--m4e.xn--2-ku7i", + "output": null + }, + { + "comment": "C2; V6", + "input": "xn--m4e.xn--2-tgnv469h", + "output": null + }, + { + "comment": "V6", + "input": "\uaaf6\u3002\u5b36\u00df\u847d", + "output": null + }, + { + "comment": "V6", + "input": "\uaaf6\u3002\u5b36SS\u847d", + "output": null + }, + { + "comment": "V6", + "input": "\uaaf6\u3002\u5b36ss\u847d", + "output": null + }, + { + "comment": "V6", + "input": "\uaaf6\u3002\u5b36Ss\u847d", + "output": null + }, + { + "comment": "V6", + "input": "xn--2v9a.xn--ss-q40dp97m", + "output": null + }, + { + "comment": "V6", + "input": "xn--2v9a.xn--zca7637b14za", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u03c2\ud805\udc3d\ud896\udc88\ud805\udf2b\uff61\ud83a\udf29\u200c\ud802\udec4", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u03c2\ud805\udc3d\ud896\udc88\ud805\udf2b\u3002\ud83a\udf29\u200c\ud802\udec4", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u03a3\ud805\udc3d\ud896\udc88\ud805\udf2b\u3002\ud83a\udf29\u200c\ud802\udec4", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u03c3\ud805\udc3d\ud896\udc88\ud805\udf2b\u3002\ud83a\udf29\u200c\ud802\udec4", + "output": null + }, + { + "comment": "V7", + "input": "xn--4xa2260lk3b8z15g.xn--tw9ct349a", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--4xa2260lk3b8z15g.xn--0ug4653g2xzf", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--3xa4260lk3b8z15g.xn--0ug4653g2xzf", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u03a3\ud805\udc3d\ud896\udc88\ud805\udf2b\uff61\ud83a\udf29\u200c\ud802\udec4", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u03c3\ud805\udc3d\ud896\udc88\ud805\udf2b\uff61\ud83a\udf29\u200c\ud802\udec4", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2ea2\ud9df\ude85\ud835\udfe4\uff61\u200d\ud83d\udeb7", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2ea2\ud9df\ude852\u3002\u200d\ud83d\udeb7", + "output": null + }, + { + "comment": "V7", + "input": "xn--2-4jtr4282f.xn--m78h", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--2-4jtr4282f.xn--1ugz946p", + "output": null + }, + { + "comment": "V6", + "input": "\ud836\ude25\u3002\u2adf\ud804\ude3e", + "output": null + }, + { + "comment": "V6", + "input": "xn--n82h.xn--63iw010f", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored); U1 (ignored)", + "input": "-\u1897\u200c\ud83c\udd04.\ud805\udf22", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored); U1 (ignored)", + "input": "-\u1897\u200c3,.\ud805\udf22", + "output": null + }, + { + "comment": "V6; V3 (ignored); U1 (ignored)", + "input": "xn---3,-3eu.xn--9h2d", + "output": null + }, + { + "comment": "C1; V6; V3 (ignored); U1 (ignored)", + "input": "xn---3,-3eu051c.xn--9h2d", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----pck1820x.xn--9h2d", + "output": null + }, + { + "comment": "C1; V6; V7; V3 (ignored)", + "input": "xn----pck312bx563c.xn--9h2d", + "output": null + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "\u17b4.\ucb87-", + "output": ".xn----938f" + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": "\u17b4.\u110d\u1170\u11ae-", + "output": ".xn----938f" + }, + { + "comment": "V3 (ignored); A4_2 (ignored)", + "input": ".xn----938f", + "output": ".xn----938f" + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--z3e.xn----938f", + "output": null + }, + { + "comment": "C1; V7", + "input": "\u200c\ud805\udcc2\u3002\u2488-\udbc2\ude9b", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\u200c\ud805\udcc2\u30021.-\udbc2\ude9b", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn--wz1d.1.xn----rg03o", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn--0ugy057g.1.xn----rg03o", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--wz1d.xn----dcp29674o", + "output": null + }, + { + "comment": "C1; V7", + "input": "xn--0ugy057g.xn----dcp29674o", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": ".xn--hcb32bni", + "output": ".xn--hcb32bni" + }, + { + "input": "xn--hcb32bni", + "output": "xn--hcb32bni" + }, + { + "input": "\u06bd\u0663\u0596", + "output": "xn--hcb32bni" + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u0f94\ua84b-\uff0e-\ud81a\udf34", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "\u0f94\ua84b-.-\ud81a\udf34", + "output": null + }, + { + "comment": "V6; V3 (ignored)", + "input": "xn----ukg9938i.xn----4u5m", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\ud9bd\udcb3-\u22e2\u200c\uff0e\u6807-", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\ud9bd\udcb3-\u2291\u0338\u200c\uff0e\u6807-", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\ud9bd\udcb3-\u22e2\u200c.\u6807-", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "\ud9bd\udcb3-\u2291\u0338\u200c.\u6807-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----9mo67451g.xn----qj7b", + "output": null + }, + { + "comment": "C1; V7; V3 (ignored)", + "input": "xn----sgn90kn5663a.xn----qj7b", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "-\ud914\ude74.\u06e0\u189a-", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----qi38c.xn----jxc827k", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": "\u3002\u0635\u0649\u0e37\u0644\u0627\u3002\u5c93\u1bf2\udb43\udf83\u1842", + "output": null + }, + { + "comment": "V7; A4_2 (ignored)", + "input": ".xn--mgb1a7bt462h.xn--17e10qe61f9r71s", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "\u188c\uff0e-\u085a", + "output": "xn--59e.xn----5jd" + }, + { + "comment": "V3 (ignored)", + "input": "\u188c.-\u085a", + "output": "xn--59e.xn----5jd" + }, + { + "comment": "V3 (ignored)", + "input": "xn--59e.xn----5jd", + "output": "xn--59e.xn----5jd" + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0e\u00df", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2.\u00df", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2.SS", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2.ss", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2.Ss", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn----9tg11172akr8b.ss", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn----9tg11172akr8b.xn--zca", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0eSS", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0ess", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0eSs", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "\u0d4d-\u200d\u200c\uff61\ud955\udfa7\u2085\u2260", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "\u0d4d-\u200d\u200c\uff61\ud955\udfa7\u2085=\u0338", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "\u0d4d-\u200d\u200c\u3002\ud955\udfa75\u2260", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "\u0d4d-\u200d\u200c\u3002\ud955\udfa75=\u0338", + "output": null + }, + { + "comment": "V6; V7; V3 (ignored)", + "input": "xn----jmf.xn--5-ufo50192e", + "output": null + }, + { + "comment": "C1; C2; V6; V7", + "input": "xn----jmf215lda.xn--5-ufo50192e", + "output": null + }, + { + "comment": "V6; V7", + "input": "\u9523\u3002\u0a4d\udb41\ude3b\udb41\ude86", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--gc5a.xn--ybc83044ppga", + "output": null + }, + { + "input": "xn--8gb2338k.xn--lhb0154f", + "output": "xn--8gb2338k.xn--lhb0154f" + }, + { + "input": "\u063d\ud804\ude3e.\u0649\ua92b", + "output": "xn--8gb2338k.xn--lhb0154f" + }, + { + "input": "\u10c1\u10b16\u0318\u3002\u00df\u1b03", + "output": "xn--6-8cb7433a2ba.xn--zca894k" + }, + { + "input": "\u2d21\u2d116\u0318\u3002\u00df\u1b03", + "output": "xn--6-8cb7433a2ba.xn--zca894k" + }, + { + "input": "\u10c1\u10b16\u0318\u3002SS\u1b03", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "\u2d21\u2d116\u0318\u3002ss\u1b03", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "\u10c1\u2d116\u0318\u3002Ss\u1b03", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "xn--6-8cb7433a2ba.xn--ss-2vq", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "\u2d21\u2d116\u0318.ss\u1b03", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "\u10c1\u10b16\u0318.SS\u1b03", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "\u10c1\u2d116\u0318.Ss\u1b03", + "output": "xn--6-8cb7433a2ba.xn--ss-2vq" + }, + { + "input": "xn--6-8cb7433a2ba.xn--zca894k", + "output": "xn--6-8cb7433a2ba.xn--zca894k" + }, + { + "input": "\u2d21\u2d116\u0318.\u00df\u1b03", + "output": "xn--6-8cb7433a2ba.xn--zca894k" + }, + { + "comment": "V7", + "input": "xn--6-8cb306hms1a.xn--ss-2vq", + "output": null + }, + { + "comment": "V7", + "input": "xn--6-8cb555h2b.xn--ss-2vq", + "output": null + }, + { + "comment": "V7", + "input": "xn--6-8cb555h2b.xn--zca894k", + "output": null + }, + { + "comment": "V7", + "input": "\ud9ee\udc50\uff61\u226f\ud804\udeea", + "output": null + }, + { + "comment": "V7", + "input": "\ud9ee\udc50\uff61>\u0338\ud804\udeea", + "output": null + }, + { + "comment": "V7", + "input": "\ud9ee\udc50\u3002\u226f\ud804\udeea", + "output": null + }, + { + "comment": "V7", + "input": "\ud9ee\udc50\u3002>\u0338\ud804\udeea", + "output": null + }, + { + "comment": "V7", + "input": "xn--eo08b.xn--hdh3385g", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": "\udb40\udd0f\ud81a\udf34\udb43\udcbd\uff61\uffa0", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": "\udb40\udd0f\ud81a\udf34\udb43\udcbd\u3002\u1160", + "output": null + }, + { + "comment": "V6; V7; A4_2 (ignored)", + "input": "xn--619ep9154c.", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--619ep9154c.xn--psd", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--619ep9154c.xn--cl7c", + "output": null + }, + { + "comment": "V7", + "input": "\udb42\udf54.\ud800\udef1\u2082", + "output": null + }, + { + "comment": "V7", + "input": "\udb42\udf54.\ud800\udef12", + "output": null + }, + { + "comment": "V7", + "input": "xn--vi56e.xn--2-w91i", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2dbf.\u00df\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2dbf.SS\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2dbf.ss\u200d", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u2dbf.Ss\u200d", + "output": null + }, + { + "comment": "V7", + "input": "xn--7pj.ss", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--7pj.xn--ss-n1t", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--7pj.xn--zca870n", + "output": null + }, + { + "comment": "C1", + "input": "\u6889\u3002\u200c", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--7zv.", + "output": "xn--7zv." + }, + { + "comment": "A4_2 (ignored)", + "input": "\u6889.", + "output": "xn--7zv." + }, + { + "comment": "C1", + "input": "xn--7zv.xn--0ug", + "output": null + }, + { + "input": "xn--iwb.ss", + "output": "xn--iwb.ss" + }, + { + "input": "\u0853.ss", + "output": "xn--iwb.ss" + }, + { + "input": "\u0853.SS", + "output": "xn--iwb.ss" + }, + { + "comment": "V7; V3 (ignored)", + "input": "\u40da\u87e5-\u3002-\ud9b5\udc98\u2488", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "\u40da\u87e5-\u3002-\ud9b5\udc981.", + "output": null + }, + { + "comment": "V7; V3 (ignored); A4_2 (ignored)", + "input": "xn----n50a258u.xn---1-up07j.", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn----n50a258u.xn----ecp33805f", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u1894\u2260\udbec\ude42.\u200d\ud800\udee2", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u1894=\u0338\udbec\ude42.\u200d\ud800\udee2", + "output": null + }, + { + "comment": "V7", + "input": "xn--ebf031cf7196a.xn--587c", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--ebf031cf7196a.xn--1ug9540g", + "output": null + }, + { + "comment": "V3 (ignored)", + "input": "-\uff61\u2e90", + "output": "-.xn--6vj" + }, + { + "comment": "V3 (ignored)", + "input": "-\u3002\u2e90", + "output": "-.xn--6vj" + }, + { + "comment": "V3 (ignored)", + "input": "-.xn--6vj", + "output": "-.xn--6vj" + }, + { + "comment": "V6; V7", + "input": "\udb43\udc29\ud807\udcac\uff0e\u065c", + "output": null + }, + { + "comment": "V6; V7", + "input": "\udb43\udc29\ud807\udcac.\u065c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--sn3d59267c.xn--4hb", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud800\udf7a.\ud928\uddc3\u200c", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--ie8c.xn--2g51a", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--ie8c.xn--0ug03366c", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u200d\u226e\uff0e\udb41\udfea\ud8a6\udecf-", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u200d<\u0338\uff0e\udb41\udfea\ud8a6\udecf-", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u200d\u226e.\udb41\udfea\ud8a6\udecf-", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "\u200d<\u0338.\udb41\udfea\ud8a6\udecf-", + "output": null + }, + { + "comment": "V7; V3 (ignored)", + "input": "xn--gdh.xn----cr99a1w710b", + "output": null + }, + { + "comment": "C2; V7; V3 (ignored)", + "input": "xn--1ug95g.xn----cr99a1w710b", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u200d\u8954\u3002\u10bc5\ua86e\ud995\udf4f", + "output": null + }, + { + "comment": "C2; V7", + "input": "\u200d\u200d\u8954\u3002\u2d1c5\ua86e\ud995\udf4f", + "output": null + }, + { + "comment": "V7", + "input": "xn--2u2a.xn--5-uws5848bpf44e", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1uga7691f.xn--5-uws5848bpf44e", + "output": null + }, + { + "comment": "V7", + "input": "xn--2u2a.xn--5-r1g7167ipfw8d", + "output": null + }, + { + "comment": "C2; V7", + "input": "xn--1uga7691f.xn--5-r1g7167ipfw8d", + "output": null + }, + { + "input": "xn--ix9c26l.xn--q0s", + "output": "xn--ix9c26l.xn--q0s" + }, + { + "input": "\ud802\udedc\ud804\udf3c.\u5a40", + "output": "xn--ix9c26l.xn--q0s" + }, + { + "comment": "C2; V6; V7", + "input": "\ua9b9\u200d\ud077\ud8af\udda1\uff61\u2082", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "\ua9b9\u200d\u110f\u1173\u11b2\ud8af\udda1\uff61\u2082", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--0m9as84e2e21c.c", + "output": null + }, + { + "comment": "C2; V6; V7", + "input": "xn--1ug1435cfkyaoi04d.c", + "output": null + }, + { + "input": "\ud802\udec0\uff0e\u0689\ud804\udf00", + "output": "xn--pw9c.xn--fjb8658k" + }, + { + "input": "\ud802\udec0.\u0689\ud804\udf00", + "output": "xn--pw9c.xn--fjb8658k" + }, + { + "input": "xn--pw9c.xn--fjb8658k", + "output": "xn--pw9c.xn--fjb8658k" + }, + { + "comment": "V6", + "input": "\u2260\u81a3\u3002\u0f83", + "output": null + }, + { + "comment": "V6", + "input": "=\u0338\u81a3\u3002\u0f83", + "output": null + }, + { + "comment": "V6", + "input": "xn--1chy468a.xn--2ed", + "output": null + }, + { + "comment": "C2", + "input": "\ud800\udef7\u3002\u200d", + "output": null + }, + { + "comment": "A4_2 (ignored)", + "input": "xn--r97c.", + "output": "xn--r97c." + }, + { + "comment": "A4_2 (ignored)", + "input": "\ud800\udef7.", + "output": "xn--r97c." + }, + { + "comment": "C2", + "input": "xn--r97c.xn--1ug", + "output": null + }, + { + "comment": "V6", + "input": "\ud807\udc33\ud804\ude2f\u3002\u296a", + "output": null + }, + { + "comment": "V6", + "input": "xn--2g1d14o.xn--jti", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud804\udd80\u4074\ud952\udde3\uff0e\u10b5\ud835\udfdc\u200c\u0348", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud804\udd80\u4074\ud952\udde3.\u10b54\u200c\u0348", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud804\udd80\u4074\ud952\udde3.\u2d154\u200c\u0348", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--1mnx647cg3x1b.xn--4-zfb5123a", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--1mnx647cg3x1b.xn--4-zfb502tlsl", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "\ud804\udd80\u4074\ud952\udde3\uff0e\u2d15\ud835\udfdc\u200c\u0348", + "output": null + }, + { + "comment": "V6; V7", + "input": "xn--1mnx647cg3x1b.xn--4-zfb324h", + "output": null + }, + { + "comment": "C1; V6; V7", + "input": "xn--1mnx647cg3x1b.xn--4-zfb324h32o", + "output": null + } +] diff --git a/Tests/LibWeb/Text/input/wpt-import/url/resources/a-element-origin.js b/Tests/LibWeb/Text/input/wpt-import/url/resources/a-element-origin.js new file mode 100644 index 00000000000..3e5e6cd0c76 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/resources/a-element-origin.js @@ -0,0 +1,39 @@ +promise_test(() => Promise.all([ + fetch("resources/urltestdata.json").then(res => res.json()), + fetch("resources/urltestdata-javascript-only.json").then(res => res.json()), +]).then((tests) => tests.flat()).then(runURLTests), "Loading data…"); + +function setBase(base) { + document.getElementById("base").href = base +} + +function bURL(url, base) { + setBase(base); + const a = document.createElement("a"); + a.setAttribute("href", url); + return a; +} + +function runURLTests(urlTests) { + for (const expected of urlTests) { + // Skip comments and tests without "origin" expectation + if (typeof expected === "string" || !("origin" in expected)) + continue; + + // Fragments are relative against "about:blank" (this might always be redundant due to requiring "origin" in expected) + if (expected.base === null && expected.input.startsWith("#")) + continue; + + // HTML special cases data: and javascript: URLs in + if (expected.base !== null && (expected.base.startsWith("data:") || expected.base.startsWith("javascript:"))) + continue; + + // We cannot use a null base for HTML tests + const base = expected.base === null ? "about:blank" : expected.base; + + test(function() { + var url = bURL(expected.input, base) + assert_equals(url.origin, expected.origin, "origin") + }, "Parsing origin: <" + expected.input + "> against <" + base + ">") + } +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/resources/setters_tests.json b/Tests/LibWeb/Text/input/wpt-import/url/resources/setters_tests.json new file mode 100644 index 00000000000..efd548b6c88 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/resources/setters_tests.json @@ -0,0 +1,2424 @@ +{ + "comment": [ + "## Tests for setters of https://url.spec.whatwg.org/#urlutils-members", + "", + "This file contains a JSON object.", + "Other than 'comment', each key is an attribute of the `URL` interface", + "defined in WHATWG’s URL Standard.", + "The values are arrays of test case objects for that attribute.", + "", + "To run a test case for the attribute `attr`:", + "", + "* Create a new `URL` object with the value for the 'href' key", + " the constructor single parameter. (Without a base URL.)", + " This must not throw.", + "* Set the attribute `attr` to (invoke its setter with)", + " with the value of for 'new_value' key.", + "* The value for the 'expected' key is another object.", + " For each `key` / `value` pair of that object,", + " get the attribute `key` (invoke its getter).", + " The returned string must be equal to `value`.", + "", + "Note: the 'href' setter is already covered by urltestdata.json." + ], + "protocol": [ + { + "comment": "The empty string is not a valid scheme. Setter leaves the URL unchanged.", + "href": "a://example.net", + "new_value": "", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "href": "a://example.net", + "new_value": "b", + "expected": { + "href": "b://example.net", + "protocol": "b:" + } + }, + { + "href": "javascript:alert(1)", + "new_value": "defuse", + "expected": { + "href": "defuse:alert(1)", + "protocol": "defuse:" + } + }, + { + "comment": "Upper-case ASCII is lower-cased", + "href": "a://example.net", + "new_value": "B", + "expected": { + "href": "b://example.net", + "protocol": "b:" + } + }, + { + "comment": "Non-ASCII is rejected", + "href": "a://example.net", + "new_value": "é", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "No leading digit", + "href": "a://example.net", + "new_value": "0b", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "No leading punctuation", + "href": "a://example.net", + "new_value": "+b", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "href": "a://example.net", + "new_value": "bC0+-.", + "expected": { + "href": "bc0+-.://example.net", + "protocol": "bc0+-.:" + } + }, + { + "comment": "Only some punctuation is acceptable", + "href": "a://example.net", + "new_value": "b,c", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "Non-ASCII is rejected", + "href": "a://example.net", + "new_value": "bé", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "Can’t switch from URL containing username/password/port to file", + "href": "http://test@example.net", + "new_value": "file", + "expected": { + "href": "http://test@example.net/", + "protocol": "http:" + } + }, + { + "href": "https://example.net:1234", + "new_value": "file", + "expected": { + "href": "https://example.net:1234/", + "protocol": "https:" + } + }, + { + "href": "wss://x:x@example.net:1234", + "new_value": "file", + "expected": { + "href": "wss://x:x@example.net:1234/", + "protocol": "wss:" + } + }, + { + "comment": "Can’t switch from file URL with no host", + "href": "file://localhost/", + "new_value": "http", + "expected": { + "href": "file:///", + "protocol": "file:" + } + }, + { + "href": "file:///test", + "new_value": "https", + "expected": { + "href": "file:///test", + "protocol": "file:" + } + }, + { + "href": "file:", + "new_value": "wss", + "expected": { + "href": "file:///", + "protocol": "file:" + } + }, + { + "comment": "Can’t switch from special scheme to non-special", + "href": "http://example.net", + "new_value": "b", + "expected": { + "href": "http://example.net/", + "protocol": "http:" + } + }, + { + "href": "file://hi/path", + "new_value": "s", + "expected": { + "href": "file://hi/path", + "protocol": "file:" + } + }, + { + "href": "https://example.net", + "new_value": "s", + "expected": { + "href": "https://example.net/", + "protocol": "https:" + } + }, + { + "href": "ftp://example.net", + "new_value": "test", + "expected": { + "href": "ftp://example.net/", + "protocol": "ftp:" + } + }, + { + "comment": "Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must.", + "href": "mailto:me@example.net", + "new_value": "http", + "expected": { + "href": "mailto:me@example.net", + "protocol": "mailto:" + } + }, + { + "comment": "Can’t switch from non-special scheme to special", + "href": "ssh://me@example.net", + "new_value": "http", + "expected": { + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://me@example.net", + "new_value": "https", + "expected": { + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://me@example.net", + "new_value": "file", + "expected": { + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://example.net", + "new_value": "file", + "expected": { + "href": "ssh://example.net", + "protocol": "ssh:" + } + }, + { + "href": "nonsense:///test", + "new_value": "https", + "expected": { + "href": "nonsense:///test", + "protocol": "nonsense:" + } + }, + { + "comment": "Stuff after the first ':' is ignored", + "href": "http://example.net", + "new_value": "https:foo : bar", + "expected": { + "href": "https://example.net/", + "protocol": "https:" + } + }, + { + "comment": "Stuff after the first ':' is ignored", + "href": "data:text/html,

Test", + "new_value": "view-source+data:foo : bar", + "expected": { + "href": "view-source+data:text/html,

Test", + "protocol": "view-source+data:" + } + }, + { + "comment": "Port is set to null if it is the default for new scheme.", + "href": "http://foo.com:443/", + "new_value": "https", + "expected": { + "href": "https://foo.com/", + "protocol": "https:", + "port": "" + } + }, + { + "comment": "Tab and newline are stripped", + "href": "http://test/", + "new_value": "h\u000D\u000Att\u0009ps", + "expected": { + "href": "https://test/", + "protocol": "https:", + "port": "" + } + }, + { + "href": "http://test/", + "new_value": "https\u000D", + "expected": { + "href": "https://test/", + "protocol": "https:" + } + }, + { + "comment": "Non-tab/newline C0 controls result in no-op", + "href": "http://test/", + "new_value": "https\u0000", + "expected": { + "href": "http://test/", + "protocol": "http:" + } + }, + { + "href": "http://test/", + "new_value": "https\u000C", + "expected": { + "href": "http://test/", + "protocol": "http:" + } + }, + { + "href": "http://test/", + "new_value": "https\u000E", + "expected": { + "href": "http://test/", + "protocol": "http:" + } + }, + { + "href": "http://test/", + "new_value": "https\u0020", + "expected": { + "href": "http://test/", + "protocol": "http:" + } + } + ], + "username": [ + { + "comment": "No host means no username", + "href": "file:///home/you/index.html", + "new_value": "me", + "expected": { + "href": "file:///home/you/index.html", + "username": "" + } + }, + { + "comment": "No host means no username", + "href": "unix:/run/foo.socket", + "new_value": "me", + "expected": { + "href": "unix:/run/foo.socket", + "username": "" + } + }, + { + "comment": "Cannot-be-a-base means no username", + "href": "mailto:you@example.net", + "new_value": "me", + "expected": { + "href": "mailto:you@example.net", + "username": "" + } + }, + { + "href": "javascript:alert(1)", + "new_value": "wario", + "expected": { + "href": "javascript:alert(1)", + "username": "" + } + }, + { + "href": "http://example.net", + "new_value": "me", + "expected": { + "href": "http://me@example.net/", + "username": "me" + } + }, + { + "href": "http://:secret@example.net", + "new_value": "me", + "expected": { + "href": "http://me:secret@example.net/", + "username": "me" + } + }, + { + "href": "http://me@example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "username": "" + } + }, + { + "href": "http://me:secret@example.net", + "new_value": "", + "expected": { + "href": "http://:secret@example.net/", + "username": "" + } + }, + { + "comment": "UTF-8 percent encoding with the userinfo encode set.", + "href": "http://example.net", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/", + "username": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is.", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://%c3%89t%C3%A9@example.net/", + "username": "%c3%89t%C3%A9" + } + }, + { + "href": "sc:///", + "new_value": "x", + "expected": { + "href": "sc:///", + "username": "" + } + }, + { + "href": "javascript://x/", + "new_value": "wario", + "expected": { + "href": "javascript://wario@x/", + "username": "wario" + } + }, + { + "href": "file://test/", + "new_value": "test", + "expected": { + "href": "file://test/", + "username": "" + } + } + ], + "password": [ + { + "comment": "No host means no password", + "href": "file:///home/me/index.html", + "new_value": "secret", + "expected": { + "href": "file:///home/me/index.html", + "password": "" + } + }, + { + "comment": "No host means no password", + "href": "unix:/run/foo.socket", + "new_value": "secret", + "expected": { + "href": "unix:/run/foo.socket", + "password": "" + } + }, + { + "comment": "Cannot-be-a-base means no password", + "href": "mailto:me@example.net", + "new_value": "secret", + "expected": { + "href": "mailto:me@example.net", + "password": "" + } + }, + { + "href": "http://example.net", + "new_value": "secret", + "expected": { + "href": "http://:secret@example.net/", + "password": "secret" + } + }, + { + "href": "http://me@example.net", + "new_value": "secret", + "expected": { + "href": "http://me:secret@example.net/", + "password": "secret" + } + }, + { + "href": "http://:secret@example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "password": "" + } + }, + { + "href": "http://me:secret@example.net", + "new_value": "", + "expected": { + "href": "http://me@example.net/", + "password": "" + } + }, + { + "comment": "UTF-8 percent encoding with the userinfo encode set.", + "href": "http://example.net", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/", + "password": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is.", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://:%c3%89t%C3%A9@example.net/", + "password": "%c3%89t%C3%A9" + } + }, + { + "href": "sc:///", + "new_value": "x", + "expected": { + "href": "sc:///", + "password": "" + } + }, + { + "href": "javascript://x/", + "new_value": "bowser", + "expected": { + "href": "javascript://:bowser@x/", + "password": "bowser" + } + }, + { + "href": "file://test/", + "new_value": "test", + "expected": { + "href": "file://test/", + "password": "" + } + } + ], + "host": [ + { + "comment": "Non-special scheme", + "href": "sc://x/", + "new_value": "\u0000", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "\u0009", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000A", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000D", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": " ", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "#", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "/", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "?", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "@", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "ß", + "expected": { + "href": "sc://%C3%9F/", + "host": "%C3%9F", + "hostname": "%C3%9F" + } + }, + { + "comment": "IDNA Nontransitional_Processing", + "href": "https://x/", + "new_value": "ß", + "expected": { + "href": "https://xn--zca/", + "host": "xn--zca", + "hostname": "xn--zca" + } + }, + { + "comment": "Cannot-be-a-base means no host", + "href": "mailto:me@example.net", + "new_value": "example.com", + "expected": { + "href": "mailto:me@example.net", + "host": "" + } + }, + { + "comment": "Cannot-be-a-base means no host", + "href": "data:text/plain,Stuff", + "new_value": "example.net", + "expected": { + "href": "data:text/plain,Stuff", + "host": "" + } + }, + { + "href": "http://example.net", + "new_value": "example.com:8080", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Port number is unchanged if not specified in the new value", + "href": "http://example.net:8080", + "new_value": "example.com", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Port number is unchanged if not specified", + "href": "http://example.net:8080", + "new_value": "example.com:", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "The empty host is not valid for special schemes", + "href": "http://example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "host": "example.net" + } + }, + { + "comment": "The empty host is OK for non-special schemes", + "href": "view-source+http://example.net/foo", + "new_value": "", + "expected": { + "href": "view-source+http:///foo", + "host": "" + } + }, + { + "comment": "Path-only URLs can gain a host", + "href": "a:/foo", + "new_value": "example.net", + "expected": { + "href": "a://example.net/foo", + "host": "example.net" + } + }, + { + "comment": "IPv4 address syntax is normalized", + "href": "http://example.net", + "new_value": "0x7F000001:8080", + "expected": { + "href": "http://127.0.0.1:8080/", + "host": "127.0.0.1:8080", + "hostname": "127.0.0.1", + "port": "8080" + } + }, + { + "comment": "IPv6 address syntax is normalized", + "href": "http://example.net", + "new_value": "[::0:01]:2", + "expected": { + "href": "http://[::1]:2/", + "host": "[::1]:2", + "hostname": "[::1]", + "port": "2" + } + }, + { + "comment": "IPv6 literal address with port, crbug.com/1012416", + "href": "http://example.net", + "new_value": "[2001:db8::2]:4002", + "expected": { + "href": "http://[2001:db8::2]:4002/", + "host": "[2001:db8::2]:4002", + "hostname": "[2001:db8::2]", + "port": "4002" + } + }, + { + "comment": "Default port number is removed", + "href": "http://example.net", + "new_value": "example.com:80", + "expected": { + "href": "http://example.com/", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Default port number is removed", + "href": "https://example.net", + "new_value": "example.com:443", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Default port number is only removed for the relevant scheme", + "href": "https://example.net", + "new_value": "example.com:80", + "expected": { + "href": "https://example.com:80/", + "host": "example.com:80", + "hostname": "example.com", + "port": "80" + } + }, + { + "comment": "Port number is removed if new port is scheme default and existing URL has a non-default port", + "href": "http://example.net:8080", + "new_value": "example.com:80", + "expected": { + "href": "http://example.com/", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com/stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080/stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com?stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored, trailing 'port'", + "href": "http://example.net/path", + "new_value": "example.com?stuff:8080", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080?stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com#stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080#stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "example.com:8080\\stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts", + "href": "view-source+http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "view-source+http://example.net/path", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "view-source+http://example.net/path", + "new_value": "example.com:8080stuff2", + "expected": { + "href": "view-source+http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "example.com:8080stuff2", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "example.com:8080+2", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net:8080", + "new_value": "example.com:invalid", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net:8080/test", + "new_value": "[::1]:invalid", + "expected": { + "href": "http://[::1]:8080/test", + "host": "[::1]:8080", + "hostname": "[::1]", + "port": "8080" + } + }, + { + "comment": "IPv6 without port", + "href": "http://example.net:8080/test", + "new_value": "[::1]", + "expected": { + "href": "http://[::1]:8080/test", + "host": "[::1]:8080", + "hostname": "[::1]", + "port": "8080" + } + }, + { + "comment": "Port numbers are 16 bit integers", + "href": "http://example.net/path", + "new_value": "example.com:65535", + "expected": { + "href": "http://example.com:65535/path", + "host": "example.com:65535", + "hostname": "example.com", + "port": "65535" + } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.", + "href": "http://example.net/path", + "new_value": "example.com:65536", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Broken IPv6", + "href": "http://example.net/", + "new_value": "[google.com]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.4x]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "file://y/", + "new_value": "x:123", + "expected": { + "href": "file://y/", + "host": "y", + "hostname": "y", + "port": "" + } + }, + { + "href": "file://y/", + "new_value": "loc%41lhost", + "expected": { + "href": "file:///", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "file://hi/x", + "new_value": "", + "expected": { + "href": "file:///x", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "sc://test@test/", + "new_value": "", + "expected": { + "href": "sc://test@test/", + "host": "test", + "hostname": "test", + "username": "test" + } + }, + { + "href": "sc://test:12/", + "new_value": "", + "expected": { + "href": "sc://test:12/", + "host": "test:12", + "hostname": "test", + "port": "12" + } + }, + { + "comment": "Leading / is not stripped", + "href": "http://example.com/", + "new_value": "///bad.com", + "expected": { + "href": "http://example.com/", + "host": "example.com", + "hostname": "example.com" + } + }, + { + "comment": "Leading / is not stripped", + "href": "sc://example.com/", + "new_value": "///bad.com", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "https://example.com/", + "new_value": "a%C2%ADb", + "expected": { + "href": "https://ab/", + "host": "ab", + "hostname": "ab" + } + }, + { + "href": "https://example.com/", + "new_value": "\u00AD", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com" + } + }, + { + "href": "https://example.com/", + "new_value": "%C2%AD", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com" + } + }, + { + "href": "https://example.com/", + "new_value": "xn--", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com" + } + } + ], + "hostname": [ + { + "comment": "Non-special scheme", + "href": "sc://x/", + "new_value": "\u0000", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "\u0009", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000A", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000D", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": " ", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "#", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "/", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "?", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "@", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "comment": "Cannot-be-a-base means no host", + "href": "mailto:me@example.net", + "new_value": "example.com", + "expected": { + "href": "mailto:me@example.net", + "host": "" + } + }, + { + "comment": "Cannot-be-a-base means no host", + "href": "data:text/plain,Stuff", + "new_value": "example.net", + "expected": { + "href": "data:text/plain,Stuff", + "host": "" + } + }, + { + "href": "http://example.net:8080", + "new_value": "example.com", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "The empty host is not valid for special schemes", + "href": "http://example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "host": "example.net" + } + }, + { + "comment": "The empty host is OK for non-special schemes", + "href": "view-source+http://example.net/foo", + "new_value": "", + "expected": { + "href": "view-source+http:///foo", + "host": "" + } + }, + { + "comment": "Path-only URLs can gain a host", + "href": "a:/foo", + "new_value": "example.net", + "expected": { + "href": "a://example.net/foo", + "host": "example.net" + } + }, + { + "comment": "IPv4 address syntax is normalized", + "href": "http://example.net:8080", + "new_value": "0x7F000001", + "expected": { + "href": "http://127.0.0.1:8080/", + "host": "127.0.0.1:8080", + "hostname": "127.0.0.1", + "port": "8080" + } + }, + { + "comment": "IPv6 address syntax is normalized", + "href": "http://example.net", + "new_value": "[::0:01]", + "expected": { + "href": "http://[::1]/", + "host": "[::1]", + "hostname": "[::1]", + "port": "" + } + }, + { + "comment": ": delimiter invalidates entire value", + "href": "http://example.net/path", + "new_value": "example.com:8080", + "expected": { + "href": "http://example.net/path", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": ": delimiter invalidates entire value", + "href": "http://example.net:8080/path", + "new_value": "example.com:", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com/stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com?stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com#stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts", + "href": "view-source+http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "view-source+http://example.net/path", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Broken IPv6", + "href": "http://example.net/", + "new_value": "[google.com]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.4x]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "file://y/", + "new_value": "x:123", + "expected": { + "href": "file://y/", + "host": "y", + "hostname": "y", + "port": "" + } + }, + { + "href": "file://y/", + "new_value": "loc%41lhost", + "expected": { + "href": "file:///", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "file://hi/x", + "new_value": "", + "expected": { + "href": "file:///x", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "sc://test@test/", + "new_value": "", + "expected": { + "href": "sc://test@test/", + "host": "test", + "hostname": "test", + "username": "test" + } + }, + { + "href": "sc://test:12/", + "new_value": "", + "expected": { + "href": "sc://test:12/", + "host": "test:12", + "hostname": "test", + "port": "12" + } + }, + { + "comment": "Drop /. from path", + "href": "non-spec:/.//p", + "new_value": "h", + "expected": { + "href": "non-spec://h//p", + "host": "h", + "hostname": "h", + "pathname": "//p" + } + }, + { + "href": "non-spec:/.//p", + "new_value": "", + "expected": { + "href": "non-spec:////p", + "host": "", + "hostname": "", + "pathname": "//p" + } + }, + { + "comment": "Leading / is not stripped", + "href": "http://example.com/", + "new_value": "///bad.com", + "expected": { + "href": "http://example.com/", + "host": "example.com", + "hostname": "example.com" + } + }, + { + "comment": "Leading / is not stripped", + "href": "sc://example.com/", + "new_value": "///bad.com", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "https://example.com/", + "new_value": "a%C2%ADb", + "expected": { + "href": "https://ab/", + "host": "ab", + "hostname": "ab" + } + }, + { + "href": "https://example.com/", + "new_value": "\u00AD", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com" + } + }, + { + "href": "https://example.com/", + "new_value": "%C2%AD", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com" + } + }, + { + "href": "https://example.com/", + "new_value": "xn--", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com" + } + } + ], + "port": [ + { + "href": "http://example.net", + "new_value": "8080", + "expected": { + "href": "http://example.net:8080/", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Port number is removed if empty is the new value", + "href": "http://example.net:8080", + "new_value": "", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Default port number is removed", + "href": "http://example.net:8080", + "new_value": "80", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Default port number is removed", + "href": "https://example.net:4433", + "new_value": "443", + "expected": { + "href": "https://example.net/", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Default port number is only removed for the relevant scheme", + "href": "https://example.net", + "new_value": "80", + "expected": { + "href": "https://example.net:80/", + "host": "example.net:80", + "hostname": "example.net", + "port": "80" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "8080/stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "8080?stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "8080#stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "8080\\stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "view-source+http://example.net/path", + "new_value": "8080stuff2", + "expected": { + "href": "view-source+http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "8080stuff2", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "8080+2", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Port numbers are 16 bit integers", + "href": "http://example.net/path", + "new_value": "65535", + "expected": { + "href": "http://example.net:65535/path", + "host": "example.net:65535", + "hostname": "example.net", + "port": "65535" + } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error", + "href": "http://example.net:8080/path", + "new_value": "65536", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Setting port to a string that doesn't parse as a number", + "href": "http://example.net:8080/path", + "new_value": "randomstring", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error", + "href": "non-special://example.net:8080/path", + "new_value": "65536", + "expected": { + "href": "non-special://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "href": "file://test/", + "new_value": "12", + "expected": { + "href": "file://test/", + "port": "" + } + }, + { + "href": "file://localhost/", + "new_value": "12", + "expected": { + "href": "file:///", + "port": "" + } + }, + { + "href": "non-base:value", + "new_value": "12", + "expected": { + "href": "non-base:value", + "port": "" + } + }, + { + "href": "sc:///", + "new_value": "12", + "expected": { + "href": "sc:///", + "port": "" + } + }, + { + "href": "sc://x/", + "new_value": "12", + "expected": { + "href": "sc://x:12/", + "port": "12" + } + }, + { + "href": "javascript://x/", + "new_value": "12", + "expected": { + "href": "javascript://x:12/", + "port": "12" + } + }, + { + "comment": "Leading u0009 on special scheme", + "href": "https://domain.com:443", + "new_value": "\u00098080", + "expected": { + "port": "8080" + } + }, + { + "comment": "Leading u0009 on non-special scheme", + "href": "wpt++://domain.com:443", + "new_value": "\u00098080", + "expected": { + "port": "8080" + } + }, + { + "comment": "Should use all ascii prefixed characters as port", + "href": "https://www.google.com:4343", + "new_value": "4wpt", + "expected": { + "port": "4" + } + } + ], + "pathname": [ + { + "comment": "Opaque paths cannot be set", + "href": "mailto:me@example.net", + "new_value": "/foo", + "expected": { + "href": "mailto:me@example.net", + "pathname": "me@example.net" + } + }, + { + "href": "data:original", + "new_value": "new value", + "expected": { + "href": "data:original", + "pathname": "original" + } + }, + { + "href": "sc:original", + "new_value": "new value", + "expected": { + "href": "sc:original", + "pathname": "original" + } + }, + { + "comment": "Special URLs cannot have their paths erased", + "href": "file:///some/path", + "new_value": "", + "expected": { + "href": "file:///", + "pathname": "/" + } + }, + { + "comment": "Non-special URLs can have their paths erased", + "href": "foo://somehost/some/path", + "new_value": "", + "expected": { + "href": "foo://somehost", + "pathname": "" + } + }, + { + "comment": "Non-special URLs with an empty host can have their paths erased", + "href": "foo:///some/path", + "new_value": "", + "expected": { + "href": "foo://", + "pathname": "" + } + }, + { + "comment": "Path-only URLs cannot have their paths erased", + "href": "foo:/some/path", + "new_value": "", + "expected": { + "href": "foo:/", + "pathname": "/" + } + }, + { + "comment": "Path-only URLs always have an initial slash", + "href": "foo:/some/path", + "new_value": "test", + "expected": { + "href": "foo:/test", + "pathname": "/test" + } + }, + { + "href": "unix:/run/foo.socket?timeout=10", + "new_value": "/var/log/../run/bar.socket", + "expected": { + "href": "unix:/var/run/bar.socket?timeout=10", + "pathname": "/var/run/bar.socket" + } + }, + { + "href": "https://example.net#nav", + "new_value": "home", + "expected": { + "href": "https://example.net/home#nav", + "pathname": "/home" + } + }, + { + "href": "https://example.net#nav", + "new_value": "../home", + "expected": { + "href": "https://example.net/home#nav", + "pathname": "/home" + } + }, + { + "comment": "\\ is a segment delimiter for 'special' URLs", + "href": "http://example.net/home?lang=fr#nav", + "new_value": "\\a\\%2E\\b\\%2e.\\c", + "expected": { + "href": "http://example.net/a/c?lang=fr#nav", + "pathname": "/a/c" + } + }, + { + "comment": "\\ is *not* a segment delimiter for non-'special' URLs", + "href": "view-source+http://example.net/home?lang=fr#nav", + "new_value": "\\a\\%2E\\b\\%2e.\\c", + "expected": { + "href": "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav", + "pathname": "/\\a\\%2E\\b\\%2e.\\c" + } + }, + { + "comment": "UTF-8 percent encoding with the default encode set. Tabs and newlines are removed.", + "href": "a:/", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]%5E_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9", + "pathname": "/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]%5E_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is, including %2E outside dotted segments.", + "href": "http://example.net", + "new_value": "%2e%2E%c3%89té", + "expected": { + "href": "http://example.net/%2e%2E%c3%89t%C3%A9", + "pathname": "/%2e%2E%c3%89t%C3%A9" + } + }, + { + "comment": "? needs to be encoded", + "href": "http://example.net", + "new_value": "?", + "expected": { + "href": "http://example.net/%3F", + "pathname": "/%3F" + } + }, + { + "comment": "# needs to be encoded", + "href": "http://example.net", + "new_value": "#", + "expected": { + "href": "http://example.net/%23", + "pathname": "/%23" + } + }, + { + "comment": "? needs to be encoded, non-special scheme", + "href": "sc://example.net", + "new_value": "?", + "expected": { + "href": "sc://example.net/%3F", + "pathname": "/%3F" + } + }, + { + "comment": "# needs to be encoded, non-special scheme", + "href": "sc://example.net", + "new_value": "#", + "expected": { + "href": "sc://example.net/%23", + "pathname": "/%23" + } + }, + { + "comment": "? doesn't mess up encoding", + "href": "http://example.net", + "new_value": "/?é", + "expected": { + "href": "http://example.net/%3F%C3%A9", + "pathname": "/%3F%C3%A9" + } + }, + { + "comment": "# doesn't mess up encoding", + "href": "http://example.net", + "new_value": "/#é", + "expected": { + "href": "http://example.net/%23%C3%A9", + "pathname": "/%23%C3%A9" + } + }, + { + "comment": "File URLs and (back)slashes", + "href": "file://monkey/", + "new_value": "\\\\", + "expected": { + "href": "file://monkey//", + "pathname": "//" + } + }, + { + "comment": "File URLs and (back)slashes", + "href": "file:///unicorn", + "new_value": "//\\/", + "expected": { + "href": "file://////", + "pathname": "////" + } + }, + { + "comment": "File URLs and (back)slashes", + "href": "file:///unicorn", + "new_value": "//monkey/..//", + "expected": { + "href": "file://///", + "pathname": "///" + } + }, + { + "comment": "Serialize /. in path", + "href": "non-spec:/", + "new_value": "/.//p", + "expected": { + "href": "non-spec:/.//p", + "pathname": "//p" + } + }, + { + "href": "non-spec:/", + "new_value": "/..//p", + "expected": { + "href": "non-spec:/.//p", + "pathname": "//p" + } + }, + { + "href": "non-spec:/", + "new_value": "//p", + "expected": { + "href": "non-spec:/.//p", + "pathname": "//p" + } + }, + { + "comment": "Drop /. from path", + "href": "non-spec:/.//", + "new_value": "p", + "expected": { + "href": "non-spec:/p", + "pathname": "/p" + } + }, + { + "comment": "Non-special URLs with non-opaque paths percent-encode U+0020", + "href": "data:/nospace", + "new_value": "space ", + "expected": { + "href": "data:/space%20", + "pathname": "/space%20" + } + }, + { + "href": "sc:/nospace", + "new_value": "space ", + "expected": { + "href": "sc:/space%20", + "pathname": "/space%20" + } + }, + { + "comment": "Trailing space should be encoded", + "href": "http://example.net", + "new_value": " ", + "expected": { + "href": "http://example.net/%20", + "pathname": "/%20" + } + }, + { + "comment": "Trailing C0 control should be encoded", + "href": "http://example.net", + "new_value": "\u0000", + "expected": { + "href": "http://example.net/%00", + "pathname": "/%00" + } + } + ], + "search": [ + { + "href": "https://example.net#nav", + "new_value": "lang=fr", + "expected": { + "href": "https://example.net/?lang=fr#nav", + "search": "?lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "lang=fr", + "expected": { + "href": "https://example.net/?lang=fr#nav", + "search": "?lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "?lang=fr", + "expected": { + "href": "https://example.net/?lang=fr#nav", + "search": "?lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "??lang=fr", + "expected": { + "href": "https://example.net/??lang=fr#nav", + "search": "??lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "?", + "expected": { + "href": "https://example.net/?#nav", + "search": "" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "", + "expected": { + "href": "https://example.net/#nav", + "search": "" + } + }, + { + "href": "https://example.net?lang=en-US", + "new_value": "", + "expected": { + "href": "https://example.net/", + "search": "" + } + }, + { + "href": "https://example.net", + "new_value": "", + "expected": { + "href": "https://example.net/", + "search": "" + } + }, + { + "comment": "UTF-8 percent encoding with the query encode set. Tabs and newlines are removed.", + "href": "a:/", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", + "search": "?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://example.net/?%c3%89t%C3%A9", + "search": "?%c3%89t%C3%A9" + } + }, + { + "comment": "Drop trailing spaces from trailing opaque paths", + "href": "data:space ?query", + "new_value": "", + "expected": { + "href": "data:space", + "pathname": "space", + "search": "" + } + }, + { + "href": "sc:space ?query", + "new_value": "", + "expected": { + "href": "sc:space", + "pathname": "space", + "search": "" + } + }, + { + "comment": "Do not drop trailing spaces from non-trailing opaque paths", + "href": "data:space ?query#fragment", + "new_value": "", + "expected": { + "href": "data:space #fragment", + "search": "" + } + }, + { + "href": "sc:space ?query#fragment", + "new_value": "", + "expected": { + "href": "sc:space #fragment", + "search": "" + } + }, + { + "comment": "Trailing space should be encoded", + "href": "http://example.net", + "new_value": " ", + "expected": { + "href": "http://example.net/?%20", + "search": "?%20" + } + }, + { + "comment": "Trailing C0 control should be encoded", + "href": "http://example.net", + "new_value": "\u0000", + "expected": { + "href": "http://example.net/?%00", + "search": "?%00" + } + } + ], + "hash": [ + { + "href": "https://example.net", + "new_value": "main", + "expected": { + "href": "https://example.net/#main", + "hash": "#main" + } + }, + { + "href": "https://example.net#nav", + "new_value": "main", + "expected": { + "href": "https://example.net/#main", + "hash": "#main" + } + }, + { + "href": "https://example.net?lang=en-US", + "new_value": "##nav", + "expected": { + "href": "https://example.net/?lang=en-US##nav", + "hash": "##nav" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "#main", + "expected": { + "href": "https://example.net/?lang=en-US#main", + "hash": "#main" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "#", + "expected": { + "href": "https://example.net/?lang=en-US#", + "hash": "" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "", + "expected": { + "href": "https://example.net/?lang=en-US", + "hash": "" + } + }, + { + "href": "http://example.net", + "new_value": "#foo bar", + "expected": { + "href": "http://example.net/#foo%20bar", + "hash": "#foo%20bar" + } + }, + { + "href": "http://example.net", + "new_value": "#foo\"bar", + "expected": { + "href": "http://example.net/#foo%22bar", + "hash": "#foo%22bar" + } + }, + { + "href": "http://example.net", + "new_value": "#foobar", + "expected": { + "href": "http://example.net/#foo%3Ebar", + "hash": "#foo%3Ebar" + } + }, + { + "href": "http://example.net", + "new_value": "#foo`bar", + "expected": { + "href": "http://example.net/#foo%60bar", + "hash": "#foo%60bar" + } + }, + { + "comment": "Simple percent-encoding; tabs and newlines are removed", + "href": "a:/", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", + "hash": "#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Percent-encode NULLs in fragment", + "href": "http://example.net", + "new_value": "a\u0000b", + "expected": { + "href": "http://example.net/#a%00b", + "hash": "#a%00b" + } + }, + { + "comment": "Percent-encode NULLs in fragment", + "href": "non-spec:/", + "new_value": "a\u0000b", + "expected": { + "href": "non-spec:/#a%00b", + "hash": "#a%00b" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://example.net/#%c3%89t%C3%A9", + "hash": "#%c3%89t%C3%A9" + } + }, + { + "href": "javascript:alert(1)", + "new_value": "castle", + "expected": { + "href": "javascript:alert(1)#castle", + "hash": "#castle" + } + }, + { + "comment": "Drop trailing spaces from trailing opaque paths", + "href": "data:space #fragment", + "new_value": "", + "expected": { + "href": "data:space", + "pathname": "space", + "hash": "" + } + }, + { + "href": "sc:space #fragment", + "new_value": "", + "expected": { + "href": "sc:space", + "pathname": "space", + "hash": "" + } + }, + { + "comment": "Do not drop trailing spaces from non-trailing opaque paths", + "href": "data:space ?query#fragment", + "new_value": "", + "expected": { + "href": "data:space ?query", + "hash": "" + } + }, + { + "href": "sc:space ?query#fragment", + "new_value": "", + "expected": { + "href": "sc:space ?query", + "hash": "" + } + }, + { + "comment": "Trailing space should be encoded", + "href": "http://example.net", + "new_value": " ", + "expected": { + "href": "http://example.net/#%20", + "hash": "#%20" + } + }, + { + "comment": "Trailing C0 control should be encoded", + "href": "http://example.net", + "new_value": "\u0000", + "expected": { + "href": "http://example.net/#%00", + "hash": "#%00" + } + } + ], + "href": [ + { + "href": "file:///var/log/system.log", + "new_value": "http://0300.168.0xF0", + "expected": { + "href": "http://192.168.0.240/", + "protocol": "http:" + } + } + ] +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata-javascript-only.json b/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata-javascript-only.json new file mode 100644 index 00000000000..a3793c1f472 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata-javascript-only.json @@ -0,0 +1,18 @@ +[ + "See ../README.md for a description of the format.", + { + "input": "http://example.com/\uD800\uD801\uDFFE\uDFFF\uFDD0\uFDCF\uFDEF\uFDF0\uFFFE\uFFFF?\uD800\uD801\uDFFE\uDFFF\uFDD0\uFDCF\uFDEF\uFDF0\uFFFE\uFFFF", + "base": null, + "href": "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF", + "search": "?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF", + "hash": "" + } +] diff --git a/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json b/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json new file mode 100644 index 00000000000..214ed0852aa --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json @@ -0,0 +1,10122 @@ +[ + "See ../README.md for a description of the format.", + { + "input": "http://example\t.\norg", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@foo:21/bar;par?b#c", + "base": "http://example.org/foo/bar", + "href": "http://user:pass@foo:21/bar;par?b#c", + "origin": "http://foo:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "foo:21", + "hostname": "foo", + "port": "21", + "pathname": "/bar;par", + "search": "?b", + "hash": "#c" + }, + { + "input": "https://test:@test", + "base": null, + "href": "https://test@test/", + "origin": "https://test", + "protocol": "https:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://:@test", + "base": null, + "href": "https://test/", + "origin": "https://test", + "protocol": "https:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://test:@test/x", + "base": null, + "href": "non-special://test@test/x", + "origin": "null", + "protocol": "non-special:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "non-special://:@test/x", + "base": null, + "href": "non-special://test/x", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "http:foo.com", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "\t :foo.com \n", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com", + "search": "", + "hash": "" + }, + { + "input": " foo.com ", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "a:\t foo.com", + "base": "http://example.org/foo/bar", + "href": "a: foo.com", + "origin": "null", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": " foo.com", + "search": "", + "hash": "" + }, + { + "input": "http://f:21/ b ? d # e ", + "base": "http://example.org/foo/bar", + "href": "http://f:21/%20b%20?%20d%20#%20e", + "origin": "http://f:21", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:21", + "hostname": "f", + "port": "21", + "pathname": "/%20b%20", + "search": "?%20d%20", + "hash": "#%20e" + }, + { + "input": "lolscheme:x x#x x", + "base": null, + "href": "lolscheme:x x#x%20x", + "protocol": "lolscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x x", + "search": "", + "hash": "#x%20x" + }, + { + "input": "http://f:/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:0/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000000000080/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:b/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: /c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:\n/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:fifty-two/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "non-special://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: 21 / b ? d # e ", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": " \t", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": ":foo.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": ":a", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:a", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:a", + "search": "", + "hash": "" + }, + { + "input": ":/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": "#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "#/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#/" + }, + { + "input": "#\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#\\", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#\\" + }, + { + "input": "#;?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#;?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#;?" + }, + { + "input": "?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": ":23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:23", + "search": "", + "hash": "" + }, + { + "input": "/:23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/:23", + "search": "", + "hash": "" + }, + { + "input": "\\x", + "base": "http://example.org/foo/bar", + "href": "http://example.org/x", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "\\\\x\\hello", + "base": "http://example.org/foo/bar", + "href": "http://x/hello", + "origin": "http://x", + "protocol": "http:", + "username": "", + "password": "", + "host": "x", + "hostname": "x", + "port": "", + "pathname": "/hello", + "search": "", + "hash": "" + }, + { + "input": "::", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::", + "search": "", + "hash": "" + }, + { + "input": "::23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::23", + "search": "", + "hash": "" + }, + { + "input": "foo://", + "base": "http://example.org/foo/bar", + "href": "foo://", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@c:29/d", + "base": "http://example.org/foo/bar", + "href": "http://a:b@c:29/d", + "origin": "http://c:29", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "c:29", + "hostname": "c", + "port": "29", + "pathname": "/d", + "search": "", + "hash": "" + }, + { + "input": "http::@c:29", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:@c:29", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:@c:29", + "search": "", + "hash": "" + }, + { + "input": "http://&a:foo(b]c@d:2/", + "base": "http://example.org/foo/bar", + "href": "http://&a:foo(b%5Dc@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "&a", + "password": "foo(b%5Dc", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://::@c@d:2", + "base": "http://example.org/foo/bar", + "href": "http://:%3A%40c@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "", + "password": "%3A%40c", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com:b@d/", + "base": "http://example.org/foo/bar", + "href": "http://foo.com:b@d/", + "origin": "http://d", + "protocol": "http:", + "username": "foo.com", + "password": "b", + "host": "d", + "hostname": "d", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com/\\@", + "base": "http://example.org/foo/bar", + "href": "http://foo.com//@", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "//@", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://foo.com/", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\a\\b:c\\d@foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://a/b:c/d@foo.com/", + "origin": "http://a", + "protocol": "http:", + "username": "", + "password": "", + "host": "a", + "hostname": "a", + "port": "", + "pathname": "/b:c/d@foo.com/", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@c\\", + "base": null, + "href": "http://a:b@c/", + "origin": "http://c", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "c", + "hostname": "c", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://a@b\\c", + "base": null, + "href": "ws://a@b/c", + "origin": "ws://b", + "protocol": "ws:", + "username": "a", + "password": "", + "host": "b", + "hostname": "b", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "foo:/", + "base": "http://example.org/foo/bar", + "href": "foo:/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "foo:/bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo:/bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo://///////", + "base": "http://example.org/foo/bar", + "href": "foo://///////", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////", + "search": "", + "hash": "" + }, + { + "input": "foo://///////bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo://///////bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:////://///", + "base": "http://example.org/foo/bar", + "href": "foo:////://///", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//://///", + "search": "", + "hash": "" + }, + { + "input": "c:/foo", + "base": "http://example.org/foo/bar", + "href": "c:/foo", + "origin": "null", + "protocol": "c:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "//foo/bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + { + "input": "http://foo/path;a??e#f#g", + "base": "http://example.org/foo/bar", + "href": "http://foo/path;a??e#f#g", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/path;a", + "search": "??e", + "hash": "#f#g" + }, + { + "input": "http://foo/abcd?efgh?ijkl", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd?efgh?ijkl", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "?efgh?ijkl", + "hash": "" + }, + { + "input": "http://foo/abcd#foo?bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd#foo?bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "", + "hash": "#foo?bar" + }, + { + "input": "[61:24:74]:98", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:24:74]:98", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:24:74]:98", + "search": "", + "hash": "" + }, + { + "input": "http:[61:27]/:foo", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:27]/:foo", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:27]/:foo", + "search": "", + "hash": "" + }, + { + "input": "http://[1::2]:3:4", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]:80", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://[2001::1]", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[::127.0.0.1]", + "base": "http://example.org/foo/bar", + "href": "http://[::7f00:1]/", + "origin": "http://[::7f00:1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::7f00:1]", + "hostname": "[::7f00:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[::127.0.0.1.]", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://[0:0:0:0:0:0:13.1.68.3]", + "base": "http://example.org/foo/bar", + "href": "http://[::d01:4403]/", + "origin": "http://[::d01:4403]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::d01:4403]", + "hostname": "[::d01:4403]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[2001::1]:80", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": "http://example.org/foo/bar", + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file://example:1/", + "base": null, + "failure": true + }, + { + "input": "file://example:test/", + "base": null, + "failure": true + }, + { + "input": "file://example%/", + "base": null, + "failure": true + }, + { + "input": "file://[example]/", + "base": null, + "failure": true + }, + { + "input": "ftps:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher:/example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": "http://example.org/foo/bar", + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher:example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": "http://example.org/foo/bar", + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "/a/b/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/b/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/b/c", + "search": "", + "hash": "" + }, + { + "input": "/a/ /c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%20/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%20/c", + "search": "", + "hash": "" + }, + { + "input": "/a%2fc", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a%2fc", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a%2fc", + "search": "", + "hash": "" + }, + { + "input": "/a/%2f/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%2f/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%2f/c", + "search": "", + "hash": "" + }, + { + "input": "#β", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#%CE%B2", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#%CE%B2" + }, + { + "input": "data:text/html,test#test", + "base": "http://example.org/foo/bar", + "href": "data:text/html,test#test", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "text/html,test", + "search": "", + "hash": "#test" + }, + { + "input": "tel:1234567890", + "base": "http://example.org/foo/bar", + "href": "tel:1234567890", + "origin": "null", + "protocol": "tel:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "1234567890", + "search": "", + "hash": "" + }, + "# Based on https://felixfbecker.github.io/whatwg-url-custom-host-repro/", + { + "input": "ssh://example.com/foo/bar.git", + "base": "http://example.org/", + "href": "ssh://example.com/foo/bar.git", + "origin": "null", + "protocol": "ssh:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/bar.git", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html", + { + "input": "file:c:\\foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:/foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": " File:c|////foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:////foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:////foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": "C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/C|\\foo\\bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "\\\\server\\file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "/\\server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "file:///foo/bar.txt", + "base": "file:///tmp/mock/path", + "href": "file:///foo/bar.txt", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo/bar.txt", + "search": "", + "hash": "" + }, + { + "input": "file:///home/me", + "base": "file:///tmp/mock/path", + "href": "file:///home/me", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/home/me", + "search": "", + "hash": "" + }, + { + "input": "//", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "file://test", + "base": "file:///tmp/mock/path", + "href": "file://test/", + "protocol": "file:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + { + "input": "file:test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + { + "input": "file:///w|m", + "base": null, + "href": "file:///w|m", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/w|m", + "search": "", + "hash": "" + }, + { + "input": "file:///w||m", + "base": null, + "href": "file:///w||m", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/w||m", + "search": "", + "hash": "" + }, + { + "input": "file:///w|/m", + "base": null, + "href": "file:///w:/m", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/w:/m", + "search": "", + "hash": "" + }, + { + "input": "file:C|/m/", + "base": null, + "href": "file:///C:/m/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/m/", + "search": "", + "hash": "" + }, + { + "input": "file:C||/m/", + "base": null, + "href": "file:///C||/m/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C||/m/", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js", + { + "input": "http://example.com/././foo", + "base": null, + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/./.foo", + "base": null, + "href": "http://example.com/.foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/.foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/.", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/./", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/..", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/..bar", + "base": null, + "href": "http://example.com/foo/..bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/..bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton", + "base": null, + "href": "http://example.com/foo/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton/../../a", + "base": null, + "href": "http://example.com/a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../..", + "base": null, + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../../ton", + "base": null, + "href": "http://example.com/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e%2", + "base": null, + "href": "http://example.com/foo/%2e%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/%2e%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar", + "base": null, + "href": "http://example.com/%2e.bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%2e.bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com////../..", + "base": null, + "href": "http://example.com//", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//../..", + "base": null, + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//..", + "base": null, + "href": "http://example.com/foo/bar/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/bar/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo", + "base": null, + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%20foo", + "base": null, + "href": "http://example.com/%20foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%20foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%", + "base": null, + "href": "http://example.com/foo%", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2", + "base": null, + "href": "http://example.com/foo%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2zbar", + "base": null, + "href": "http://example.com/foo%2zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2©zbar", + "base": null, + "href": "http://example.com/foo%2%C3%82%C2%A9zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2%C3%82%C2%A9zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%41%7a", + "base": null, + "href": "http://example.com/foo%41%7a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%41%7a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\t\u0091%91", + "base": null, + "href": "http://example.com/foo%C2%91%91", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%C2%91%91", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%00%51", + "base": null, + "href": "http://example.com/foo%00%51", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%00%51", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/(%28:%3A%29)", + "base": null, + "href": "http://example.com/(%28:%3A%29)", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/(%28:%3A%29)", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%3A%3a%3C%3c", + "base": null, + "href": "http://example.com/%3A%3a%3C%3c", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%3A%3a%3C%3c", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\tbar", + "base": null, + "href": "http://example.com/foobar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foobar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com\\\\foo\\\\bar", + "base": null, + "href": "http://example.com//foo//bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//foo//bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "base": null, + "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/@asdf%40", + "base": null, + "href": "http://example.com/@asdf%40", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/@asdf%40", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/你好你好", + "base": null, + "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/‥/foo", + "base": null, + "href": "http://example.com/%E2%80%A5/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%A5/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com//foo", + "base": null, + "href": "http://example.com/%EF%BB%BF/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%EF%BB%BF/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/‮/foo/‭/bar", + "base": null, + "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js", + { + "input": "http://www.google.com/foo?bar=baz#", + "base": null, + "href": "http://www.google.com/foo?bar=baz#", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "" + }, + { + "input": "http://www.google.com/foo?bar=baz# »", + "base": null, + "href": "http://www.google.com/foo?bar=baz#%20%C2%BB", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "#%20%C2%BB" + }, + { + "input": "data:test# »", + "base": null, + "href": "data:test#%20%C2%BB", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "#%20%C2%BB" + }, + { + "input": "http://www.google.com", + "base": null, + "href": "http://www.google.com/", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.0x00A80001", + "base": null, + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo%2Ehtml", + "base": null, + "href": "http://www/foo%2Ehtml", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo%2Ehtml", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo/%2E/html", + "base": null, + "href": "http://www/foo/html", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo/html", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@/", + "base": null, + "failure": true + }, + { + "input": "http://%25DOMAIN:foobar@foodomain.com/", + "base": null, + "href": "http://%25DOMAIN:foobar@foodomain.com/", + "origin": "http://foodomain.com", + "protocol": "http:", + "username": "%25DOMAIN", + "password": "foobar", + "host": "foodomain.com", + "hostname": "foodomain.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\www.google.com\\foo", + "base": null, + "href": "http://www.google.com/foo", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://foo:80/", + "base": null, + "href": "http://foo/", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:81/", + "base": null, + "href": "http://foo:81/", + "origin": "http://foo:81", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "httpa://foo:80/", + "base": null, + "href": "httpa://foo:80/", + "origin": "null", + "protocol": "httpa:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:-80/", + "base": null, + "failure": true + }, + { + "input": "https://foo:443/", + "base": null, + "href": "https://foo/", + "origin": "https://foo", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://foo:80/", + "base": null, + "href": "https://foo:80/", + "origin": "https://foo:80", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:21/", + "base": null, + "href": "ftp://foo/", + "origin": "ftp://foo", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:80/", + "base": null, + "href": "ftp://foo:80/", + "origin": "ftp://foo:80", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:70/", + "base": null, + "href": "gopher://foo:70/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo:70", + "hostname": "foo", + "port": "70", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:443/", + "base": null, + "href": "gopher://foo:443/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:80/", + "base": null, + "href": "ws://foo/", + "origin": "ws://foo", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:81/", + "base": null, + "href": "ws://foo:81/", + "origin": "ws://foo:81", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:443/", + "base": null, + "href": "ws://foo:443/", + "origin": "ws://foo:443", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:815/", + "base": null, + "href": "ws://foo:815/", + "origin": "ws://foo:815", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:80/", + "base": null, + "href": "wss://foo:80/", + "origin": "wss://foo:80", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:81/", + "base": null, + "href": "wss://foo:81/", + "origin": "wss://foo:81", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:443/", + "base": null, + "href": "wss://foo/", + "origin": "wss://foo", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:815/", + "base": null, + "href": "wss://foo:815/", + "origin": "wss://foo:815", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": null, + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": null, + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": null, + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": null, + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": null, + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:/example.com/", + "base": null, + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": null, + "href": "gopher:/example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": null, + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": null, + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": null, + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": null, + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": null, + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": null, + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": null, + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": null, + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": null, + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": null, + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": null, + "href": "gopher:example.com/", + "origin": "null", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": null, + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": null, + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": null, + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": null, + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": null, + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "https://example.com/aaa/bbb/%2e%2e?query", + "base": null, + "href": "https://example.com/aaa/?query", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/aaa/", + "search": "?query", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html", + { + "input": "http:@www.example.com", + "base": null, + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/@www.example.com", + "base": null, + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@www.example.com", + "base": null, + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:a:b@www.example.com", + "base": null, + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/a:b@www.example.com", + "base": null, + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@www.example.com", + "base": null, + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@pple.com", + "base": null, + "href": "http://pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http::b@www.example.com", + "base": null, + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:b@www.example.com", + "base": null, + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://:b@www.example.com", + "base": null, + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://user@/www.example.com", + "base": null, + "failure": true + }, + { + "input": "http:@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:/@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://@/www.example.com", + "base": null, + "failure": true + }, + { + "input": "https:@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:a:b@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:/a:b@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://a:b@/www.example.com", + "base": null, + "failure": true + }, + { + "input": "http::@/www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:a:@www.example.com", + "base": null, + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/a:@www.example.com", + "base": null, + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:@www.example.com", + "base": null, + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www.@pple.com", + "base": null, + "href": "http://www.@pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "www.", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:@:www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http:/@:www.example.com", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "http://@:www.example.com", + "base": null, + "failure": true + }, + { + "input": "http://:@www.example.com", + "base": null, + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Others", + { + "input": "/", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": ".", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "./test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../aaa/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/aaa/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/aaa/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "中/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/%E4%B8%AD/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/%E4%B8%AD/test.txt", + "search": "", + "hash": "" + }, + { + "input": "http://www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "//www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:...", + "base": "http://www.example.com/test", + "href": "file:///...", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/...", + "search": "", + "hash": "" + }, + { + "input": "file:..", + "base": "http://www.example.com/test", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:a", + "base": "http://www.example.com/test", + "href": "file:///a", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "file:.", + "base": null, + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:.", + "base": "http://www.example.com/test", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html", + "Basic canonicalization, uppercase should be converted to lowercase", + { + "input": "http://ExAmPlE.CoM", + "base": "http://other.com/", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example example.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://Goo%20 goo%7C|.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[:]", + "base": "http://other.com/", + "failure": true + }, + "U+3000 is mapped to U+0020 (space) which is disallowed", + { + "input": "http://GOO\u00a0\u3000goo.com", + "base": "http://other.com/", + "failure": true + }, + "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored", + { + "input": "http://GOO\u200b\u2060\ufeffgoo.com", + "base": "http://other.com/", + "href": "http://googoo.com/", + "origin": "http://googoo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "googoo.com", + "hostname": "googoo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Leading and trailing C0 control or space", + { + "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ", + "base": null, + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)", + { + "input": "http://www.foo。bar.com", + "base": "http://other.com/", + "href": "http://www.foo.bar.com/", + "origin": "http://www.foo.bar.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.foo.bar.com", + "hostname": "www.foo.bar.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0", + { + "input": "http://\ufdd0zyx.com", + "base": "http://other.com/", + "failure": true + }, + "This is the same as previous but escaped", + { + "input": "http://%ef%b7%90zyx.com", + "base": "http://other.com/", + "failure": true + }, + "U+FFFD", + { + "input": "https://\ufffd", + "base": null, + "failure": true + }, + { + "input": "https://%EF%BF%BD", + "base": null, + "failure": true + }, + { + "input": "https://x/\ufffd?\ufffd#\ufffd", + "base": null, + "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD", + "origin": "https://x", + "protocol": "https:", + "username": "", + "password": "", + "host": "x", + "hostname": "x", + "port": "", + "pathname": "/%EF%BF%BD", + "search": "?%EF%BF%BD", + "hash": "#%EF%BF%BD" + }, + "Domain is ASCII, but a label is invalid IDNA", + { + "input": "http://a.b.c.xn--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://10.0.0.xn--pokxncvks", + "base": null, + "failure": true + }, + "IDNA labels should be matched case-insensitively", + { + "input": "http://a.b.c.XN--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://a.b.c.Xn--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://10.0.0.XN--pokxncvks", + "base": null, + "failure": true + }, + { + "input": "http://10.0.0.xN--pokxncvks", + "base": null, + "failure": true + }, + "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.", + { + "input": "http://Go.com", + "base": "http://other.com/", + "href": "http://go.com/", + "origin": "http://go.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "go.com", + "hostname": "go.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257", + { + "input": "http://%41.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com", + "base": "http://other.com/", + "failure": true + }, + "...%00 in fullwidth should fail (also as escaped UTF-8 input)", + { + "input": "http://%00.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com", + "base": "http://other.com/", + "failure": true + }, + "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN", + { + "input": "http://你好你好", + "base": "http://other.com/", + "href": "http://xn--6qqa088eba/", + "origin": "http://xn--6qqa088eba", + "protocol": "http:", + "username": "", + "password": "", + "host": "xn--6qqa088eba", + "hostname": "xn--6qqa088eba", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://faß.ExAmPlE/", + "base": null, + "href": "https://xn--fa-hia.example/", + "origin": "https://xn--fa-hia.example", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--fa-hia.example", + "hostname": "xn--fa-hia.example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://faß.ExAmPlE/", + "base": null, + "href": "sc://fa%C3%9F.ExAmPlE/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "fa%C3%9F.ExAmPlE", + "hostname": "fa%C3%9F.ExAmPlE", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191", + { + "input": "http://%zz%66%a.com", + "base": "http://other.com/", + "failure": true + }, + "If we get an invalid character that has been escaped.", + { + "input": "http://%25", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://hello%00", + "base": "http://other.com/", + "failure": true + }, + "Escaped numbers should be treated like IP addresses if they are.", + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.0.257", + "base": "http://other.com/", + "failure": true + }, + "Invalid escaping in hosts causes failure", + { + "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01", + "base": "http://other.com/", + "failure": true + }, + "A space in a host causes failure", + { + "input": "http://192.168.0.1 hello", + "base": "http://other.com/", + "failure": true + }, + { + "input": "https://x x:12", + "base": null, + "failure": true + }, + "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP", + { + "input": "http://0Xc0.0250.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Domains with empty labels", + { + "input": "http://./", + "base": null, + "href": "http://./", + "origin": "http://.", + "protocol": "http:", + "username": "", + "password": "", + "host": ".", + "hostname": ".", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://../", + "base": null, + "href": "http://../", + "origin": "http://..", + "protocol": "http:", + "username": "", + "password": "", + "host": "..", + "hostname": "..", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Non-special domains with empty labels", + { + "input": "h://.", + "base": null, + "href": "h://.", + "origin": "null", + "protocol": "h:", + "username": "", + "password": "", + "host": ".", + "hostname": ".", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + "Broken IPv6", + { + "input": "http://[www.google.com]/", + "base": null, + "failure": true + }, + { + "input": "http://[google.com]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.4x]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::.1.2]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::.1]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::%31]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%5B::1]", + "base": "http://other.com/", + "failure": true + }, + "Misc Unicode", + { + "input": "http://foo:💩@example.com/bar", + "base": "http://other.com/", + "href": "http://foo:%F0%9F%92%A9@example.com/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "foo", + "password": "%F0%9F%92%A9", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + "# resolving a fragment against any scheme succeeds", + { + "input": "#", + "base": "test:test", + "href": "test:test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "" + }, + { + "input": "#x", + "base": "mailto:x@x.com", + "href": "mailto:x@x.com#x", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x@x.com", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "data:,", + "href": "data:,#x", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ",", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "about:blank", + "href": "about:blank#x", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blank", + "search": "", + "hash": "#x" + }, + { + "input": "#x:y", + "base": "about:blank", + "href": "about:blank#x:y", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blank", + "search": "", + "hash": "#x:y" + }, + { + "input": "#", + "base": "test:test?test", + "href": "test:test?test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "?test", + "hash": "" + }, + "# multiple @ in authority state", + { + "input": "https://@test@test@example:800/", + "base": "http://doesnotmatter/", + "href": "https://%40test%40test@example:800/", + "origin": "https://example:800", + "protocol": "https:", + "username": "%40test%40test", + "password": "", + "host": "example:800", + "hostname": "example", + "port": "800", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://@@@example", + "base": "http://doesnotmatter/", + "href": "https://%40%40@example/", + "origin": "https://example", + "protocol": "https:", + "username": "%40%40", + "password": "", + "host": "example", + "hostname": "example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "non-az-09 characters", + { + "input": "http://`{}:`{}@h/`{}?`{}", + "base": "http://doesnotmatter/", + "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}", + "origin": "http://h", + "protocol": "http:", + "username": "%60%7B%7D", + "password": "%60%7B%7D", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/%60%7B%7D", + "search": "?`{}", + "hash": "" + }, + "byte is ' and url is special", + { + "input": "http://host/?'", + "base": null, + "href": "http://host/?%27", + "origin": "http://host", + "protocol": "http:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/", + "search": "?%27", + "hash": "" + }, + { + "input": "notspecial://host/?'", + "base": null, + "href": "notspecial://host/?'", + "origin": "null", + "protocol": "notspecial:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/", + "search": "?'", + "hash": "" + }, + "# Credentials in base", + { + "input": "/some/path", + "base": "http://user@example.org/smth", + "href": "http://user@example.org/some/path", + "origin": "http://example.org", + "protocol": "http:", + "username": "user", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/smth", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/smth", + "search": "", + "hash": "" + }, + { + "input": "/some/path", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/some/path", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + "# a set of tests designed by zcorpan for relative URLs with unknown schemes", + { + "input": "i", + "base": "sc:sd", + "failure": true + }, + { + "input": "i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "i", + "base": "sc:/pa/pa", + "href": "sc:/pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc:///pa/pa", + "href": "sc:///pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "?i", + "base": "sc:sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc://ho/pa", + "href": "sc://ho/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "#i", + "base": "sc:sd", + "href": "sc:sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:sd/sd", + "href": "sc:sd/sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd/sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc://ho/pa", + "href": "sc://ho/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + "# make sure that relative URL logic works on known typically non-relative schemes too", + { + "input": "about:/../", + "base": null, + "href": "about:/", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/../", + "base": null, + "href": "data:/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/../", + "base": null, + "href": "javascript:/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/../", + "base": null, + "href": "mailto:/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# unknown schemes and their hosts", + { + "input": "sc://ñ.test/", + "base": null, + "href": "sc://%C3%B1.test/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1.test", + "hostname": "%C3%B1.test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://%/", + "base": null, + "href": "sc://%/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%", + "hostname": "%", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://@/", + "base": null, + "failure": true + }, + { + "input": "sc://te@s:t@/", + "base": null, + "failure": true + }, + { + "input": "sc://:/", + "base": null, + "failure": true + }, + { + "input": "sc://:12/", + "base": null, + "failure": true + }, + { + "input": "x", + "base": "sc://ñ", + "href": "sc://%C3%B1/x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + "# unknown schemes and backslashes", + { + "input": "sc:\\../", + "base": null, + "href": "sc:\\../", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\../", + "search": "", + "hash": "" + }, + "# unknown scheme with path looking like a password", + { + "input": "sc::a@example.net", + "base": null, + "href": "sc::a@example.net", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ":a@example.net", + "search": "", + "hash": "" + }, + "# unknown scheme with bogus percent-encoding", + { + "input": "wow:%NBD", + "base": null, + "href": "wow:%NBD", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%NBD", + "search": "", + "hash": "" + }, + { + "input": "wow:%1G", + "base": null, + "href": "wow:%1G", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%1G", + "search": "", + "hash": "" + }, + "# unknown scheme with non-URL characters", + { + "input": "wow:\uFFFF", + "base": null, + "href": "wow:%EF%BF%BF", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%EF%BF%BF", + "search": "", + "hash": "" + }, + "Forbidden host code points", + { + "input": "sc://a\u0000b/", + "base": null, + "failure": true + }, + { + "input": "sc://a b/", + "base": null, + "failure": true + }, + { + "input": "sc://ab", + "base": null, + "failure": true + }, + { + "input": "sc://a[b/", + "base": null, + "failure": true + }, + { + "input": "sc://a\\b/", + "base": null, + "failure": true + }, + { + "input": "sc://a]b/", + "base": null, + "failure": true + }, + { + "input": "sc://a^b", + "base": null, + "failure": true + }, + { + "input": "sc://a|b/", + "base": null, + "failure": true + }, + "Forbidden host codepoints: tabs and newlines are removed during preprocessing", + { + "input": "foo://ho\u0009st/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href":"foo://host/", + "password": "", + "pathname": "/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "foo://ho\u000Ast/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href":"foo://host/", + "password": "", + "pathname": "/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "foo://ho\u000Dst/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href":"foo://host/", + "password": "", + "pathname": "/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "" + }, + "Forbidden domain code-points", + { + "input": "http://a\u0000b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0001b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0002b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0003b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0004b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0005b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0006b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0007b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0008b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Bb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Cb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Eb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u000Fb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0010b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0011b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0012b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0013b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0014b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0015b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0016b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0017b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0018b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u0019b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Ab/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Bb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Cb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Db/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Eb/", + "base": null, + "failure": true + }, + { + "input": "http://a\u001Fb/", + "base": null, + "failure": true + }, + { + "input": "http://a b/", + "base": null, + "failure": true + }, + { + "input": "http://a%b/", + "base": null, + "failure": true + }, + { + "input": "http://ab", + "base": null, + "failure": true + }, + { + "input": "http://a[b/", + "base": null, + "failure": true + }, + { + "input": "http://a]b/", + "base": null, + "failure": true + }, + { + "input": "http://a^b", + "base": null, + "failure": true + }, + { + "input": "http://a|b/", + "base": null, + "failure": true + }, + { + "input": "http://a\u007Fb/", + "base": null, + "failure": true + }, + "Forbidden domain codepoints: tabs and newlines are removed during preprocessing", + { + "input": "http://ho\u0009st/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href":"http://host/", + "password": "", + "pathname": "/", + "port":"", + "protocol": "http:", + "search": "", + "username": "" + }, + { + "input": "http://ho\u000Ast/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href":"http://host/", + "password": "", + "pathname": "/", + "port":"", + "protocol": "http:", + "search": "", + "username": "" + }, + { + "input": "http://ho\u000Dst/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href":"http://host/", + "password": "", + "pathname": "/", + "port":"", + "protocol": "http:", + "search": "", + "username": "" + }, + "Encoded forbidden domain codepoints in special URLs", + { + "input": "http://ho%00st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%01st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%02st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%03st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%04st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%05st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%06st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%07st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%08st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%09st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Ast/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Bst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Dst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Est/", + "base": null, + "failure": true + }, + { + "input": "http://ho%0Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%10st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%11st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%12st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%13st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%14st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%15st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%16st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%17st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%18st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%19st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Ast/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Bst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Dst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Est/", + "base": null, + "failure": true + }, + { + "input": "http://ho%1Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%20st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%23st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%25st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%2Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Ast/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Est/", + "base": null, + "failure": true + }, + { + "input": "http://ho%3Fst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%40st/", + "base": null, + "failure": true + }, + { + "input": "http://ho%5Bst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%5Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%5Dst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%7Cst/", + "base": null, + "failure": true + }, + { + "input": "http://ho%7Fst/", + "base": null, + "failure": true + }, + "Allowed host/domain code points", + { + "input": "http://!\"$&'()*+,-.;=_`{}~/", + "base": null, + "href": "http://!\"$&'()*+,-.;=_`{}~/", + "origin": "http://!\"$&'()*+,-.;=_`{}~", + "protocol": "http:", + "username": "", + "password": "", + "host": "!\"$&'()*+,-.;=_`{}~", + "hostname": "!\"$&'()*+,-.;=_`{}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000B\u000C\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u007F!\"$%&'()*+,-.;=_`{}~/", + "base": null, + "href": "sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~", + "hostname": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Hosts and percent-encoding", + { + "input": "ftp://example.com%80/", + "base": null, + "failure": true + }, + { + "input": "ftp://example.com%A0/", + "base": null, + "failure": true + }, + { + "input": "https://example.com%80/", + "base": null, + "failure": true + }, + { + "input": "https://example.com%A0/", + "base": null, + "failure": true + }, + { + "input": "ftp://%e2%98%83", + "base": null, + "href": "ftp://xn--n3h/", + "origin": "ftp://xn--n3h", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://%e2%98%83", + "base": null, + "href": "https://xn--n3h/", + "origin": "https://xn--n3h", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# tests from jsdom/whatwg-url designed for code coverage", + { + "input": "http://127.0.0.1:10100/relative_import.html", + "base": null, + "href": "http://127.0.0.1:10100/relative_import.html", + "origin": "http://127.0.0.1:10100", + "protocol": "http:", + "username": "", + "password": "", + "host": "127.0.0.1:10100", + "hostname": "127.0.0.1", + "port": "10100", + "pathname": "/relative_import.html", + "search": "", + "hash": "" + }, + { + "input": "http://facebook.com/?foo=%7B%22abc%22", + "base": null, + "href": "http://facebook.com/?foo=%7B%22abc%22", + "origin": "http://facebook.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "facebook.com", + "hostname": "facebook.com", + "port": "", + "pathname": "/", + "search": "?foo=%7B%22abc%22", + "hash": "" + }, + { + "input": "https://localhost:3000/jqueryui@1.2.3", + "base": null, + "href": "https://localhost:3000/jqueryui@1.2.3", + "origin": "https://localhost:3000", + "protocol": "https:", + "username": "", + "password": "", + "host": "localhost:3000", + "hostname": "localhost", + "port": "3000", + "pathname": "/jqueryui@1.2.3", + "search": "", + "hash": "" + }, + "# tab/LF/CR", + { + "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg", + "base": null, + "href": "http://host:9000/path?query#frag", + "origin": "http://host:9000", + "protocol": "http:", + "username": "", + "password": "", + "host": "host:9000", + "hostname": "host", + "port": "9000", + "pathname": "/path", + "search": "?query", + "hash": "#frag" + }, + "# Stringification of URL.searchParams", + { + "input": "?a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "?a=b&c=d", + "searchParams": "a=b&c=d", + "hash": "" + }, + { + "input": "??a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar??a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "??a=b&c=d", + "searchParams": "%3Fa=b&c=d", + "hash": "" + }, + "# Scheme only", + { + "input": "http:", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "searchParams": "", + "hash": "" + }, + { + "input": "http:", + "base": "https://example.org/foo/bar", + "failure": true + }, + { + "input": "sc:", + "base": "https://example.org/foo/bar", + "href": "sc:", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "searchParams": "", + "hash": "" + }, + "# Percent encoding of fragments", + { + "input": "http://foo.bar/baz?qux#foo\bbar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%08bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%08bar" + }, + { + "input": "http://foo.bar/baz?qux#foo\"bar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%22bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%22bar" + }, + { + "input": "http://foo.bar/baz?qux#foobar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%3Ebar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%3Ebar" + }, + { + "input": "http://foo.bar/baz?qux#foo`bar", + "base": null, + "href": "http://foo.bar/baz?qux#foo%60bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%60bar" + }, + "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)", + { + "input": "http://1.2.3.4/", + "base": "http://other.com/", + "href": "http://1.2.3.4/", + "origin": "http://1.2.3.4", + "protocol": "http:", + "username": "", + "password": "", + "host": "1.2.3.4", + "hostname": "1.2.3.4", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://1.2.3.4./", + "base": "http://other.com/", + "href": "http://1.2.3.4/", + "origin": "http://1.2.3.4", + "protocol": "http:", + "username": "", + "password": "", + "host": "1.2.3.4", + "hostname": "1.2.3.4", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257", + "base": "http://other.com/", + "href": "http://192.168.1.1/", + "origin": "http://192.168.1.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.1.1", + "hostname": "192.168.1.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257.", + "base": "http://other.com/", + "href": "http://192.168.1.1/", + "origin": "http://192.168.1.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.1.1", + "hostname": "192.168.1.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257.com", + "base": "http://other.com/", + "href": "http://192.168.257.com/", + "origin": "http://192.168.257.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.257.com", + "hostname": "192.168.257.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256", + "base": "http://other.com/", + "href": "http://0.0.1.0/", + "origin": "http://0.0.1.0", + "protocol": "http:", + "username": "", + "password": "", + "host": "0.0.1.0", + "hostname": "0.0.1.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256.com", + "base": "http://other.com/", + "href": "http://256.com/", + "origin": "http://256.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "256.com", + "hostname": "256.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999", + "base": "http://other.com/", + "href": "http://59.154.201.255/", + "origin": "http://59.154.201.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "59.154.201.255", + "hostname": "59.154.201.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999.", + "base": "http://other.com/", + "href": "http://59.154.201.255/", + "origin": "http://59.154.201.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "59.154.201.255", + "hostname": "59.154.201.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999.com", + "base": "http://other.com/", + "href": "http://999999999.com/", + "origin": "http://999999999.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "999999999.com", + "hostname": "999999999.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://10000000000", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://10000000000.com", + "base": "http://other.com/", + "href": "http://10000000000.com/", + "origin": "http://10000000000.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "10000000000.com", + "hostname": "10000000000.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967295", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967296", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://0xffffffff", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0xffffffff1", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256", + "base": "http://other.com/", + "failure": true + }, + { + "input": "https://0x.0x.0", + "base": null, + "href": "https://0.0.0.0/", + "origin": "https://0.0.0.0", + "protocol": "https:", + "username": "", + "password": "", + "host": "0.0.0.0", + "hostname": "0.0.0.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)", + { + "input": "https://0x100000000/test", + "base": null, + "failure": true + }, + { + "input": "https://256.0.0.1/test", + "base": null, + "failure": true + }, + "# file URLs containing percent-encoded Windows drive letters (shouldn't work)", + { + "input": "file:///C%3A/", + "base": null, + "href": "file:///C%3A/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%3A/", + "search": "", + "hash": "" + }, + { + "input": "file:///C%7C/", + "base": null, + "href": "file:///C%7C/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%7C/", + "search": "", + "hash": "" + }, + { + "input": "file://%43%3A", + "base": null, + "failure": true + }, + { + "input": "file://%43%7C", + "base": null, + "failure": true + }, + { + "input": "file://%43|", + "base": null, + "failure": true + }, + { + "input": "file://C%7C", + "base": null, + "failure": true + }, + { + "input": "file://%43%7C/", + "base": null, + "failure": true + }, + { + "input": "https://%43%7C/", + "base": null, + "failure": true + }, + { + "input": "asdf://%43|/", + "base": null, + "failure": true + }, + { + "input": "asdf://%43%7C/", + "base": null, + "href": "asdf://%43%7C/", + "origin": "null", + "protocol": "asdf:", + "username": "", + "password": "", + "host": "%43%7C", + "hostname": "%43%7C", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)", + { + "input": "pix/submit.gif", + "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html", + "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///C:/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# More file URL tests by zcorpan and annevk", + { + "input": "/", + "base": "file:///C:/a/b", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "file://h/C:/a/b", + "href": "file://h/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "file://h/a/b", + "href": "file://h/", + "protocol": "file:", + "username": "", + "password": "", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "//d:", + "base": "file:///C:/a/b", + "href": "file:///d:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:", + "search": "", + "hash": "" + }, + { + "input": "//d:/..", + "base": "file:///C:/a/b", + "href": "file:///d:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///ab:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///1:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "file:", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "file:?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + { + "input": "file:#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + "# File URLs and many (back)slashes", + { + "input": "file:\\\\//", + "base": null, + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\\\\\", + "base": null, + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\\\\\?fox", + "base": null, + "href": "file:////?fox", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "?fox", + "hash": "" + }, + { + "input": "file:\\\\\\\\#guppy", + "base": null, + "href": "file:////#guppy", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "#guppy" + }, + { + "input": "file://spider///", + "base": null, + "href": "file://spider///", + "protocol": "file:", + "username": "", + "password": "", + "host": "spider", + "hostname": "spider", + "port": "", + "pathname": "///", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\localhost//", + "base": null, + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "file:///localhost//cat", + "base": null, + "href": "file:///localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "file://\\/localhost//cat", + "base": null, + "href": "file:////localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "file://localhost//a//../..//", + "base": null, + "href": "file://///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///", + "search": "", + "hash": "" + }, + { + "input": "/////mouse", + "base": "file:///elephant", + "href": "file://///mouse", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///mouse", + "search": "", + "hash": "" + }, + { + "input": "\\//pig", + "base": "file://lion/", + "href": "file:///pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pig", + "search": "", + "hash": "" + }, + { + "input": "\\/localhost//pig", + "base": "file://lion/", + "href": "file:////pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//pig", + "search": "", + "hash": "" + }, + { + "input": "//localhost//pig", + "base": "file://lion/", + "href": "file:////pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//pig", + "search": "", + "hash": "" + }, + { + "input": "/..//localhost//pig", + "base": "file://lion/", + "href": "file://lion//localhost//pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "lion", + "hostname": "lion", + "port": "", + "pathname": "//localhost//pig", + "search": "", + "hash": "" + }, + { + "input": "file://", + "base": "file://ape/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# File URLs with non-empty hosts", + { + "input": "/rooibos", + "base": "file://tea/", + "href": "file://tea/rooibos", + "protocol": "file:", + "username": "", + "password": "", + "host": "tea", + "hostname": "tea", + "port": "", + "pathname": "/rooibos", + "search": "", + "hash": "" + }, + { + "input": "/?chai", + "base": "file://tea/", + "href": "file://tea/?chai", + "protocol": "file:", + "username": "", + "password": "", + "host": "tea", + "hostname": "tea", + "port": "", + "pathname": "/", + "search": "?chai", + "hash": "" + }, + "# Windows drive letter handling with the 'file:' base URL", + { + "input": "C|", + "base": "file://host/dir/file", + "href": "file://host/C:", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|", + "base": "file://host/D:/dir1/dir2/file", + "href": "file://host/C:", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|#", + "base": "file://host/dir/file", + "href": "file://host/C:#", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|?", + "base": "file://host/dir/file", + "href": "file://host/C:?", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|/", + "base": "file://host/dir/file", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\n/", + "base": "file://host/dir/file", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\\", + "base": "file://host/dir/file", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C", + "base": "file://host/dir/file", + "href": "file://host/dir/C", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C", + "search": "", + "hash": "" + }, + { + "input": "C|a", + "base": "file://host/dir/file", + "href": "file://host/dir/C|a", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C|a", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk in the file slash state", + { + "input": "/c:/foo/bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/c|/foo/bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "file:\\c:\\foo\\bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/c:/foo/bar", + "base": "file://host/path", + "href": "file://host/c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + "# Do not drop the host in the presence of a drive letter", + { + "input": "file://example.net/C:/", + "base": null, + "href": "file://example.net/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "example.net", + "hostname": "example.net", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://1.2.3.4/C:/", + "base": null, + "href": "file://1.2.3.4/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "1.2.3.4", + "hostname": "1.2.3.4", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://[1::8]/C:/", + "base": null, + "href": "file://[1::8]/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "[1::8]", + "hostname": "[1::8]", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Copy the host from the base URL in the following cases", + { + "input": "C|/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "/C:/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file:C:/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file:/C:/", + "base": "file://host/", + "href": "file://host/C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Copy the empty host from the input in the following cases", + { + "input": "//C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "///C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file:///C:/", + "base": "file://host/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk (no host)", + { + "input": "file:/C|/", + "base": null, + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://C|/", + "base": null, + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# file URLs without base URL by Rimas Misevičius", + { + "input": "file:", + "base": null, + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:?q=v", + "base": null, + "href": "file:///?q=v", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "?q=v", + "hash": "" + }, + { + "input": "file:#frag", + "base": null, + "href": "file:///#frag", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "#frag" + }, + "# file: drive letter cases from https://crbug.com/1078698", + { + "input": "file:///Y:", + "base": null, + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "file:///Y:/", + "base": null, + "href": "file:///Y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y", + "base": null, + "href": "file:///Y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y", + "search": "", + "hash": "" + }, + { + "input": "file:///./Y:", + "base": null, + "href": "file:///Y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/Y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\Y:", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "# file: drive letter cases from https://crbug.com/1078698 but lowercased", + { + "input": "file:///y:", + "base": null, + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "file:///y:/", + "base": null, + "href": "file:///y:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:/", + "search": "", + "hash": "" + }, + { + "input": "file:///./y", + "base": null, + "href": "file:///y", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y", + "search": "", + "hash": "" + }, + { + "input": "file:///./y:", + "base": null, + "href": "file:///y:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/y:", + "search": "", + "hash": "" + }, + { + "input": "\\\\\\.\\y:", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "# Additional file URL tests for (https://github.com/whatwg/url/issues/405)", + { + "input": "file://localhost//a//../..//foo", + "base": null, + "href": "file://///foo", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///foo", + "search": "", + "hash": "" + }, + { + "input": "file://localhost////foo", + "base": null, + "href": "file://////foo", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "////foo", + "search": "", + "hash": "" + }, + { + "input": "file:////foo", + "base": null, + "href": "file:////foo", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//foo", + "search": "", + "hash": "" + }, + { + "input": "file:///one/two", + "base": "file:///", + "href": "file:///one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/one/two", + "search": "", + "hash": "" + }, + { + "input": "file:////one/two", + "base": "file:///", + "href": "file:////one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//one/two", + "search": "", + "hash": "" + }, + { + "input": "//one/two", + "base": "file:///", + "href": "file://one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "one", + "hostname": "one", + "port": "", + "pathname": "/two", + "search": "", + "hash": "" + }, + { + "input": "///one/two", + "base": "file:///", + "href": "file:///one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/one/two", + "search": "", + "hash": "" + }, + { + "input": "////one/two", + "base": "file:///", + "href": "file:////one/two", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//one/two", + "search": "", + "hash": "" + }, + { + "input": "file:///.//", + "base": "file:////", + "href": "file:////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + "File URL tests for https://github.com/whatwg/url/issues/549", + { + "input": "file:.//p", + "base": null, + "href": "file:////p", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//p", + "search": "", + "hash": "" + }, + { + "input": "file:/.//p", + "base": null, + "href": "file:////p", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//p", + "search": "", + "hash": "" + }, + "# IPv6 tests", + { + "input": "http://[1:0::]", + "base": "http://example.net/", + "href": "http://[1::]/", + "origin": "http://[1::]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1::]", + "hostname": "[1::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[0:1:2:3:4:5:6:7:8]", + "base": "http://example.net/", + "failure": true + }, + { + "input": "https://[0::0::0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:.0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:0:]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1.00.0.0.0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1.290.0.0.0]", + "base": null, + "failure": true + }, + { + "input": "https://[0:1.23.23]", + "base": null, + "failure": true + }, + "# Empty host", + { + "input": "http://?", + "base": null, + "failure": true + }, + { + "input": "http://#", + "base": null, + "failure": true + }, + "Port overflow (2^32 + 81)", + { + "input": "http://f:4294967377/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^64 + 81)", + { + "input": "http://f:18446744073709551697/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^128 + 81)", + { + "input": "http://f:340282366920938463463374607431768211537/c", + "base": "http://example.org/", + "failure": true + }, + "# Non-special-URL path tests", + { + "input": "sc://ñ", + "base": null, + "href": "sc://%C3%B1", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "sc://ñ?x", + "base": null, + "href": "sc://%C3%B1?x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "?x", + "hash": "" + }, + { + "input": "sc://ñ#x", + "base": null, + "href": "sc://%C3%B1#x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "sc://ñ", + "href": "sc://%C3%B1#x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "#x" + }, + { + "input": "?x", + "base": "sc://ñ", + "href": "sc://%C3%B1?x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "?x", + "hash": "" + }, + { + "input": "sc://?", + "base": null, + "href": "sc://?", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "sc://#", + "base": null, + "href": "sc://#", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "sc://x/", + "href": "sc:///", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "////", + "base": "sc://x/", + "href": "sc:////", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "////x/", + "base": "sc://x/", + "href": "sc:////x/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//x/", + "search": "", + "hash": "" + }, + { + "input": "tftp://foobar.com/someconfig;mode=netascii", + "base": null, + "href": "tftp://foobar.com/someconfig;mode=netascii", + "origin": "null", + "protocol": "tftp:", + "username": "", + "password": "", + "host": "foobar.com", + "hostname": "foobar.com", + "port": "", + "pathname": "/someconfig;mode=netascii", + "search": "", + "hash": "" + }, + { + "input": "telnet://user:pass@foobar.com:23/", + "base": null, + "href": "telnet://user:pass@foobar.com:23/", + "origin": "null", + "protocol": "telnet:", + "username": "user", + "password": "pass", + "host": "foobar.com:23", + "hostname": "foobar.com", + "port": "23", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ut2004://10.10.10.10:7777/Index.ut2", + "base": null, + "href": "ut2004://10.10.10.10:7777/Index.ut2", + "origin": "null", + "protocol": "ut2004:", + "username": "", + "password": "", + "host": "10.10.10.10:7777", + "hostname": "10.10.10.10", + "port": "7777", + "pathname": "/Index.ut2", + "search": "", + "hash": "" + }, + { + "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "base": null, + "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "origin": "null", + "protocol": "redis:", + "username": "foo", + "password": "bar", + "host": "somehost:6379", + "hostname": "somehost", + "port": "6379", + "pathname": "/0", + "search": "?baz=bam&qux=baz", + "hash": "" + }, + { + "input": "rsync://foo@host:911/sup", + "base": null, + "href": "rsync://foo@host:911/sup", + "origin": "null", + "protocol": "rsync:", + "username": "foo", + "password": "", + "host": "host:911", + "hostname": "host", + "port": "911", + "pathname": "/sup", + "search": "", + "hash": "" + }, + { + "input": "git://github.com/foo/bar.git", + "base": null, + "href": "git://github.com/foo/bar.git", + "origin": "null", + "protocol": "git:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar.git", + "search": "", + "hash": "" + }, + { + "input": "irc://myserver.com:6999/channel?passwd", + "base": null, + "href": "irc://myserver.com:6999/channel?passwd", + "origin": "null", + "protocol": "irc:", + "username": "", + "password": "", + "host": "myserver.com:6999", + "hostname": "myserver.com", + "port": "6999", + "pathname": "/channel", + "search": "?passwd", + "hash": "" + }, + { + "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "base": null, + "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "origin": "null", + "protocol": "dns:", + "username": "", + "password": "", + "host": "fw.example.org:9999", + "hostname": "fw.example.org", + "port": "9999", + "pathname": "/foo.bar.org", + "search": "?type=TXT", + "hash": "" + }, + { + "input": "ldap://localhost:389/ou=People,o=JNDITutorial", + "base": null, + "href": "ldap://localhost:389/ou=People,o=JNDITutorial", + "origin": "null", + "protocol": "ldap:", + "username": "", + "password": "", + "host": "localhost:389", + "hostname": "localhost", + "port": "389", + "pathname": "/ou=People,o=JNDITutorial", + "search": "", + "hash": "" + }, + { + "input": "git+https://github.com/foo/bar", + "base": null, + "href": "git+https://github.com/foo/bar", + "origin": "null", + "protocol": "git+https:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "urn:ietf:rfc:2648", + "base": null, + "href": "urn:ietf:rfc:2648", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "ietf:rfc:2648", + "search": "", + "hash": "" + }, + { + "input": "tag:joe@example.org,2001:foo/bar", + "base": null, + "href": "tag:joe@example.org,2001:foo/bar", + "origin": "null", + "protocol": "tag:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "joe@example.org,2001:foo/bar", + "search": "", + "hash": "" + }, + "Serialize /. in path", + { + "input": "non-spec:/.//", + "base": null, + "href": "non-spec:/.//", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/..//", + "base": null, + "href": "non-spec:/.//", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/a/..//", + "base": null, + "href": "non-spec:/.//", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/.//path", + "base": null, + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/..//path", + "base": null, + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "non-spec:/a/..//path", + "base": null, + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "/.//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "/..//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "..//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "a/..//path", + "base": "non-spec:/p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "non-spec:/..//p", + "href": "non-spec:/.//p", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//p", + "search": "", + "hash": "" + }, + { + "input": "path", + "base": "non-spec:/..//p", + "href": "non-spec:/.//path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//path", + "search": "", + "hash": "" + }, + "Do not serialize /. in path", + { + "input": "../path", + "base": "non-spec:/.//p", + "href": "non-spec:/path", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + "# percent encoded hosts in non-special-URLs", + { + "input": "non-special://%E2%80%A0/", + "base": null, + "href": "non-special://%E2%80%A0/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "%E2%80%A0", + "hostname": "%E2%80%A0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://H%4fSt/path", + "base": null, + "href": "non-special://H%4fSt/path", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "H%4fSt", + "hostname": "H%4fSt", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + "# IPv6 in non-special-URLs", + { + "input": "non-special://[1:2:0:0:5:0:0:0]/", + "base": null, + "href": "non-special://[1:2:0:0:5::]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2:0:0:5::]", + "hostname": "[1:2:0:0:5::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2:0:0:0:0:0:3]/", + "base": null, + "href": "non-special://[1:2::3]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]", + "hostname": "[1:2::3]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2::3]:80/", + "base": null, + "href": "non-special://[1:2::3]:80/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]:80", + "hostname": "[1:2::3]", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[:80/", + "base": null, + "failure": true + }, + { + "input": "blob:https://example.com:443/", + "base": null, + "href": "blob:https://example.com:443/", + "origin": "https://example.com", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "https://example.com:443/", + "search": "", + "hash": "" + }, + { + "input": "blob:http://example.org:88/", + "base": null, + "href": "blob:http://example.org:88/", + "origin": "http://example.org:88", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "http://example.org:88/", + "search": "", + "hash": "" + }, + { + "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "base": null, + "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf", + "search": "", + "hash": "" + }, + { + "input": "blob:", + "base": null, + "href": "blob:", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + "blob: in blob:", + { + "input": "blob:blob:", + "base": null, + "href": "blob:blob:", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blob:", + "search": "", + "hash": "" + }, + { + "input": "blob:blob:https://example.org/", + "base": null, + "href": "blob:blob:https://example.org/", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blob:https://example.org/", + "search": "", + "hash": "" + }, + "Non-http(s): in blob:", + { + "input": "blob:about:blank", + "base": null, + "href": "blob:about:blank", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "about:blank", + "search": "", + "hash": "" + }, + { + "input": "blob:file://host/path", + "base": null, + "href": "blob:file://host/path", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "file://host/path", + "search": "", + "hash": "" + }, + { + "input": "blob:ftp://host/path", + "base": null, + "href": "blob:ftp://host/path", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "ftp://host/path", + "search": "", + "hash": "" + }, + { + "input": "blob:ws://example.org/", + "base": null, + "href": "blob:ws://example.org/", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "ws://example.org/", + "search": "", + "hash": "" + }, + { + "input": "blob:wss://example.org/", + "base": null, + "href": "blob:wss://example.org/", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "wss://example.org/", + "search": "", + "hash": "" + }, + "Percent-encoded http: in blob:", + { + "input": "blob:http%3a//example.org/", + "base": null, + "href": "blob:http%3a//example.org/", + "origin": "null", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "http%3a//example.org/", + "search": "", + "hash": "" + }, + "Invalid IPv4 radix digits", + { + "input": "http://0x7f.0.0.0x7g", + "base": null, + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0X7F.0.0.0X7G", + "base": null, + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid IPv4 portion of IPv6 address", + { + "input": "http://[::127.0.0.0.1]", + "base": null, + "failure": true + }, + "Uncompressed IPv6 addresses with 0", + { + "input": "http://[0:1:0:1:0:1:0:1]", + "base": null, + "href": "http://[0:1:0:1:0:1:0:1]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[0:1:0:1:0:1:0:1]", + "hostname": "[0:1:0:1:0:1:0:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[1:0:1:0:1:0:1:0]", + "base": null, + "href": "http://[1:0:1:0:1:0:1:0]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1:0:1:0:1:0:1:0]", + "hostname": "[1:0:1:0:1:0:1:0]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Percent-encoded query and fragment", + { + "input": "http://example.org/test?\u0022", + "base": null, + "href": "http://example.org/test?%22", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%22", + "hash": "" + }, + { + "input": "http://example.org/test?\u0023", + "base": null, + "href": "http://example.org/test?#", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "http://example.org/test?\u003C", + "base": null, + "href": "http://example.org/test?%3C", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3C", + "hash": "" + }, + { + "input": "http://example.org/test?\u003E", + "base": null, + "href": "http://example.org/test?%3E", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3E", + "hash": "" + }, + { + "input": "http://example.org/test?\u2323", + "base": null, + "href": "http://example.org/test?%E2%8C%A3", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%E2%8C%A3", + "hash": "" + }, + { + "input": "http://example.org/test?%23%23", + "base": null, + "href": "http://example.org/test?%23%23", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%23%23", + "hash": "" + }, + { + "input": "http://example.org/test?%GH", + "base": null, + "href": "http://example.org/test?%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%GH", + "hash": "" + }, + { + "input": "http://example.org/test?a#%EF", + "base": null, + "href": "http://example.org/test?a#%EF", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%EF" + }, + { + "input": "http://example.org/test?a#%GH", + "base": null, + "href": "http://example.org/test?a#%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%GH" + }, + "URLs that require a non-about:blank base. (Also serve as invalid base tests.)", + { + "input": "a", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "a/", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "a//", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "Bases that don't fail to parse but fail to be bases", + { + "input": "test-a-colon.html", + "base": "a:", + "failure": true + }, + { + "input": "test-a-colon-b.html", + "base": "a:b", + "failure": true + }, + "Other base URL tests, that must succeed", + { + "input": "test-a-colon-slash.html", + "base": "a:/", + "href": "a:/test-a-colon-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash.html", + "base": "a://", + "href": "a:///test-a-colon-slash-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-b.html", + "base": "a:/b", + "href": "a:/test-a-colon-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-b.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash-b.html", + "base": "a://b", + "href": "a://b/test-a-colon-slash-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "b", + "hostname": "b", + "port": "", + "pathname": "/test-a-colon-slash-slash-b.html", + "search": "", + "hash": "" + }, + "Null code point in fragment", + { + "input": "http://example.org/test?a#b\u0000c", + "base": null, + "href": "http://example.org/test?a#b%00c", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + { + "input": "non-spec://example.org/test?a#b\u0000c", + "base": null, + "href": "non-spec://example.org/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + { + "input": "non-spec:/test?a#b\u0000c", + "base": null, + "href": "non-spec:/test?a#b%00c", + "protocol": "non-spec:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#b%00c" + }, + "First scheme char - not allowed: https://github.com/whatwg/url/issues/464", + { + "input": "10.0.0.7:8080/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/10.0.0.7:8080/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/10.0.0.7:8080/foo.html", + "search": "", + "hash": "" + }, + "Subsequent scheme chars - not allowed", + { + "input": "a!@$*=/foo.html", + "base": "file:///some/dir/bar.html", + "href": "file:///some/dir/a!@$*=/foo.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/some/dir/a!@$*=/foo.html", + "search": "", + "hash": "" + }, + "First and subsequent scheme chars - allowed", + { + "input": "a1234567890-+.:foo/bar", + "base": "http://example.com/dir/file", + "href": "a1234567890-+.:foo/bar", + "protocol": "a1234567890-+.:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "foo/bar", + "search": "", + "hash": "" + }, + "IDNA ignored code points in file URLs hosts", + { + "input": "file://a\u00ADb/p", + "base": null, + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + { + "input": "file://a%C2%ADb/p", + "base": null, + "href": "file://ab/p", + "protocol": "file:", + "username": "", + "password": "", + "host": "ab", + "hostname": "ab", + "port": "", + "pathname": "/p", + "search": "", + "hash": "" + }, + "IDNA hostnames which get mapped to 'localhost'", + { + "input": "file://loC𝐀𝐋𝐇𝐨𝐬𝐭/usr/bin", + "base": null, + "href": "file:///usr/bin", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/usr/bin", + "search": "", + "hash": "" + }, + "Empty host after the domain to ASCII", + { + "input": "file://\u00ad/p", + "base": null, + "failure": true + }, + { + "input": "file://%C2%AD/p", + "base": null, + "failure": true + }, + { + "input": "file://xn--/p", + "base": null, + "failure": true + }, + "https://bugzilla.mozilla.org/show_bug.cgi?id=1647058", + { + "input": "#link", + "base": "https://example.org/##link", + "href": "https://example.org/#link", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "#link" + }, + "UTF-8 percent-encode of C0 control percent-encode set and supersets", + { + "input": "non-special:cannot-be-a-base-url-\u0000\u0001\u001F\u001E\u007E\u007F\u0080", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80", + "origin": "null", + "password": "", + "pathname": "cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:cannot-be-a-base-url-!\"$%&'()*+,-.;<=>@[\\]^_`{|}~@/", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:cannot-be-a-base-url-!\"$%&'()*+,-.;<=>@[\\]^_`{|}~@/", + "origin": "null", + "password": "", + "pathname": "cannot-be-a-base-url-!\"$%&'()*+,-.;<=>@[\\]^_`{|}~@/", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "https://www.example.com/path{\u007Fpath.html?query'\u007F=query#fragment<\u007Ffragment", + "base": null, + "hash": "#fragment%3C%7Ffragment", + "host": "www.example.com", + "hostname": "www.example.com", + "href": "https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment", + "origin": "https://www.example.com", + "password": "", + "pathname": "/path%7B%7Fpath.html", + "port": "", + "protocol": "https:", + "search": "?query%27%7F=query", + "username": "" + }, + { + "input": "https://user:pass[\u007F@foo/bar", + "base": "http://example.org", + "hash": "", + "host": "foo", + "hostname": "foo", + "href": "https://user:pass%5B%7F@foo/bar", + "origin": "https://foo", + "password": "pass%5B%7F", + "pathname": "/bar", + "port": "", + "protocol": "https:", + "search": "", + "username": "user" + }, + "Tests for the distinct percent-encode sets", + { + "input": "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/", + "origin": "null", + "password": "", + "pathname": "/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~" + }, + { + "input": "wss:// !\"$%&'()*+,-.;<=>@[]^_`{|}~@host/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/", + "origin": "wss://host", + "password": "", + "pathname": "/", + "port":"", + "protocol": "wss:", + "search": "", + "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~" + }, + { + "input": "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/", + "origin": "null", + "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~", + "pathname": "/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "joe" + }, + { + "input": "wss://joe: !\"$%&'()*+,-.:;<=>@[]^_`{|}~@host/", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/", + "origin": "wss://host", + "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~", + "pathname": "/", + "port":"", + "protocol": "wss:", + "search": "", + "username": "joe" + }, + { + "input": "foo://!\"$%&'()*+,-.;=_`{}~/", + "base": null, + "hash": "", + "host": "!\"$%&'()*+,-.;=_`{}~", + "hostname": "!\"$%&'()*+,-.;=_`{}~", + "href":"foo://!\"$%&'()*+,-.;=_`{}~/", + "origin": "null", + "password": "", + "pathname": "/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "wss://!\"$&'()*+,-.;=_`{}~/", + "base": null, + "hash": "", + "host": "!\"$&'()*+,-.;=_`{}~", + "hostname": "!\"$&'()*+,-.;=_`{}~", + "href":"wss://!\"$&'()*+,-.;=_`{}~/", + "origin": "wss://!\"$&'()*+,-.;=_`{}~", + "password": "", + "pathname": "/", + "port":"", + "protocol": "wss:", + "search": "", + "username": "" + }, + { + "input": "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]%5E_%60%7B|%7D~", + "origin": "null", + "password": "", + "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]%5E_%60%7B|%7D~", + "port":"", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "wss://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B|%7D~", + "origin": "wss://host", + "password": "", + "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B|%7D~", + "port":"", + "protocol": "wss:", + "search": "", + "username": "" + }, + { + "input": "foo://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "origin": "null", + "password": "", + "pathname": "/dir/", + "port":"", + "protocol": "foo:", + "search": "?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "username": "" + }, + { + "input": "wss://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "", + "host": "host", + "hostname": "host", + "href": "wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "origin": "wss://host", + "password": "", + "pathname": "/dir/", + "port":"", + "protocol": "wss:", + "search": "?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~", + "username": "" + }, + { + "input": "foo://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~", + "host": "host", + "hostname": "host", + "href": "foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~", + "origin": "null", + "password": "", + "pathname": "/dir/", + "port":"", + "protocol": "foo:", + "search": "", + "username": "" + }, + { + "input": "wss://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", + "base": null, + "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~", + "host": "host", + "hostname": "host", + "href": "wss://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~", + "origin": "wss://host", + "password": "", + "pathname": "/dir/", + "port":"", + "protocol": "wss:", + "search": "", + "username": "" + }, + "Ensure that input schemes are not ignored when resolving non-special URLs", + { + "input": "abc:rootless", + "base": "abc://host/path", + "hash": "", + "host": "", + "hostname": "", + "href":"abc:rootless", + "password": "", + "pathname": "rootless", + "port":"", + "protocol": "abc:", + "search": "", + "username": "" + }, + { + "input": "abc:rootless", + "base": "abc:/path", + "hash": "", + "host": "", + "hostname": "", + "href":"abc:rootless", + "password": "", + "pathname": "rootless", + "port":"", + "protocol": "abc:", + "search": "", + "username": "" + }, + { + "input": "abc:rootless", + "base": "abc:path", + "hash": "", + "host": "", + "hostname": "", + "href":"abc:rootless", + "password": "", + "pathname": "rootless", + "port":"", + "protocol": "abc:", + "search": "", + "username": "" + }, + { + "input": "abc:/rooted", + "base": "abc://host/path", + "hash": "", + "host": "", + "hostname": "", + "href":"abc:/rooted", + "password": "", + "pathname": "/rooted", + "port":"", + "protocol": "abc:", + "search": "", + "username": "" + }, + "Empty query and fragment with blank should throw an error", + { + "input": "#", + "base": null, + "failure": true, + "relativeTo": "any-base" + }, + { + "input": "?", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + "Last component looks like a number, but not valid IPv4", + { + "input": "http://1.2.3.4.5", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://1.2.3.4.5.", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://0..0x300/", + "base": null, + "failure": true + }, + { + "input": "http://0..0x300./", + "base": null, + "failure": true + }, + { + "input": "http://256.256.256.256.256", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256.256.", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://1.2.3.08", + "base": null, + "failure": true + }, + { + "input": "http://1.2.3.08.", + "base": null, + "failure": true + }, + { + "input": "http://1.2.3.09", + "base": null, + "failure": true + }, + { + "input": "http://09.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://09.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://01.2.3.4.5", + "base": null, + "failure": true + }, + { + "input": "http://01.2.3.4.5.", + "base": null, + "failure": true + }, + { + "input": "http://0x100.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://0x100.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://0x1.2.3.4.5", + "base": null, + "failure": true + }, + { + "input": "http://0x1.2.3.4.5.", + "base": null, + "failure": true + }, + { + "input": "http://foo.1.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://foo.1.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://foo.2.3.4", + "base": null, + "failure": true + }, + { + "input": "http://foo.2.3.4.", + "base": null, + "failure": true + }, + { + "input": "http://foo.09", + "base": null, + "failure": true + }, + { + "input": "http://foo.09.", + "base": null, + "failure": true + }, + { + "input": "http://foo.0x4", + "base": null, + "failure": true + }, + { + "input": "http://foo.0x4.", + "base": null, + "failure": true + }, + { + "input": "http://foo.09..", + "base": null, + "hash": "", + "host": "foo.09..", + "hostname": "foo.09..", + "href":"http://foo.09../", + "password": "", + "pathname": "/", + "port":"", + "protocol": "http:", + "search": "", + "username": "" + }, + { + "input": "http://0999999999999999999/", + "base": null, + "failure": true + }, + { + "input": "http://foo.0x", + "base": null, + "failure": true + }, + { + "input": "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123", + "base": null, + "failure": true + }, + { + "input": "http://💩.123/", + "base": null, + "failure": true + }, + "U+0000 and U+FFFF in various places", + { + "input": "https://\u0000y", + "base": null, + "failure": true + }, + { + "input": "https://x/\u0000y", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/%00y", + "password": "", + "pathname": "/%00y", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://x/?\u0000y", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/?%00y", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "?%00y", + "username": "" + }, + { + "input": "https://x/?#\u0000y", + "base": null, + "hash": "#%00y", + "host": "x", + "hostname": "x", + "href": "https://x/?#%00y", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://\uFFFFy", + "base": null, + "failure": true + }, + { + "input": "https://x/\uFFFFy", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/%EF%BF%BFy", + "password": "", + "pathname": "/%EF%BF%BFy", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://x/?\uFFFFy", + "base": null, + "hash": "", + "host": "x", + "hostname": "x", + "href": "https://x/?%EF%BF%BFy", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "?%EF%BF%BFy", + "username": "" + }, + { + "input": "https://x/?#\uFFFFy", + "base": null, + "hash": "#%EF%BF%BFy", + "host": "x", + "hostname": "x", + "href": "https://x/?#%EF%BF%BFy", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "non-special:\u0000y", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:%00y", + "password": "", + "pathname": "%00y", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/\u0000y", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/%00y", + "password": "", + "pathname": "x/%00y", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/?\u0000y", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/?%00y", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "?%00y", + "username": "" + }, + { + "input": "non-special:x/?#\u0000y", + "base": null, + "hash": "#%00y", + "host": "", + "hostname": "", + "href": "non-special:x/?#%00y", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:\uFFFFy", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:%EF%BF%BFy", + "password": "", + "pathname": "%EF%BF%BFy", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/\uFFFFy", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/%EF%BF%BFy", + "password": "", + "pathname": "x/%EF%BF%BFy", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "non-special:x/?\uFFFFy", + "base": null, + "hash": "", + "host": "", + "hostname": "", + "href": "non-special:x/?%EF%BF%BFy", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "?%EF%BF%BFy", + "username": "" + }, + { + "input": "non-special:x/?#\uFFFFy", + "base": null, + "hash": "#%EF%BF%BFy", + "host": "", + "hostname": "", + "href": "non-special:x/?#%EF%BF%BFy", + "password": "", + "pathname": "x/", + "port": "", + "protocol": "non-special:", + "search": "", + "username": "" + }, + { + "input": "", + "base": null, + "failure": true, + "relativeTo": "non-opaque-path-base" + }, + { + "input": "https://example.com/\"quoted\"", + "base": null, + "hash": "", + "host": "example.com", + "hostname": "example.com", + "href": "https://example.com/%22quoted%22", + "origin": "https://example.com", + "password": "", + "pathname": "/%22quoted%22", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "input": "https://a%C2%ADb/", + "base": null, + "hash": "", + "host": "ab", + "hostname": "ab", + "href": "https://ab/", + "origin": "https://ab", + "password": "", + "pathname": "/", + "port": "", + "protocol": "https:", + "search": "", + "username": "" + }, + { + "comment": "Empty host after domain to ASCII", + "input": "https://\u00AD/", + "base": null, + "failure": true + }, + { + "input": "https://%C2%AD/", + "base": null, + "failure": true + }, + { + "input": "https://xn--/", + "base": null, + "failure": true + }, + "Non-special schemes that some implementations might incorrectly treat as special", + { + "input": "data://example.com:8080/pathname?search#hash", + "base": null, + "href": "data://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "data:///test", + "base": null, + "href": "data:///test", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "data://test/a/../b", + "base": null, + "href": "data://test/b", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "data://:443", + "base": null, + "failure": true + }, + { + "input": "data://test:test", + "base": null, + "failure": true + }, + { + "input": "data://[:1]", + "base": null, + "failure": true + }, + { + "input": "javascript://example.com:8080/pathname?search#hash", + "base": null, + "href": "javascript://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "javascript:///test", + "base": null, + "href": "javascript:///test", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "javascript://test/a/../b", + "base": null, + "href": "javascript://test/b", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "javascript://:443", + "base": null, + "failure": true + }, + { + "input": "javascript://test:test", + "base": null, + "failure": true + }, + { + "input": "javascript://[:1]", + "base": null, + "failure": true + }, + { + "input": "mailto://example.com:8080/pathname?search#hash", + "base": null, + "href": "mailto://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "mailto:///test", + "base": null, + "href": "mailto:///test", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "mailto://test/a/../b", + "base": null, + "href": "mailto://test/b", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "mailto://:443", + "base": null, + "failure": true + }, + { + "input": "mailto://test:test", + "base": null, + "failure": true + }, + { + "input": "mailto://[:1]", + "base": null, + "failure": true + }, + { + "input": "intent://example.com:8080/pathname?search#hash", + "base": null, + "href": "intent://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "intent:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "intent:///test", + "base": null, + "href": "intent:///test", + "origin": "null", + "protocol": "intent:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "intent://test/a/../b", + "base": null, + "href": "intent://test/b", + "origin": "null", + "protocol": "intent:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "intent://:443", + "base": null, + "failure": true + }, + { + "input": "intent://test:test", + "base": null, + "failure": true + }, + { + "input": "intent://[:1]", + "base": null, + "failure": true + }, + { + "input": "urn://example.com:8080/pathname?search#hash", + "base": null, + "href": "urn://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "urn:///test", + "base": null, + "href": "urn:///test", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "urn://test/a/../b", + "base": null, + "href": "urn://test/b", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "urn://:443", + "base": null, + "failure": true + }, + { + "input": "urn://test:test", + "base": null, + "failure": true + }, + { + "input": "urn://[:1]", + "base": null, + "failure": true + }, + { + "input": "turn://example.com:8080/pathname?search#hash", + "base": null, + "href": "turn://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "turn:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "turn:///test", + "base": null, + "href": "turn:///test", + "origin": "null", + "protocol": "turn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "turn://test/a/../b", + "base": null, + "href": "turn://test/b", + "origin": "null", + "protocol": "turn:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "turn://:443", + "base": null, + "failure": true + }, + { + "input": "turn://test:test", + "base": null, + "failure": true + }, + { + "input": "turn://[:1]", + "base": null, + "failure": true + }, + { + "input": "stun://example.com:8080/pathname?search#hash", + "base": null, + "href": "stun://example.com:8080/pathname?search#hash", + "origin": "null", + "protocol": "stun:", + "username": "", + "password": "", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080", + "pathname": "/pathname", + "search": "?search", + "hash": "#hash" + }, + { + "input": "stun:///test", + "base": null, + "href": "stun:///test", + "origin": "null", + "protocol": "stun:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "stun://test/a/../b", + "base": null, + "href": "stun://test/b", + "origin": "null", + "protocol": "stun:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/b", + "search": "", + "hash": "" + }, + { + "input": "stun://:443", + "base": null, + "failure": true + }, + { + "input": "stun://test:test", + "base": null, + "failure": true + }, + { + "input": "stun://[:1]", + "base": null, + "failure": true + }, + { + "input": "w://x:0", + "base": null, + "href": "w://x:0", + "origin": "null", + "protocol": "w:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "west://x:0", + "base": null, + "href": "west://x:0", + "origin": "null", + "protocol": "west:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "android://x:0/a", + "base": null, + "href": "android://x:0/a", + "origin": "null", + "protocol": "android:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "drivefs://x:0/a", + "base": null, + "href": "drivefs://x:0/a", + "origin": "null", + "protocol": "drivefs:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "chromeos-steam://x:0/a", + "base": null, + "href": "chromeos-steam://x:0/a", + "origin": "null", + "protocol": "chromeos-steam:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "steam://x:0/a", + "base": null, + "href": "steam://x:0/a", + "origin": "null", + "protocol": "steam:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "materialized-view://x:0/a", + "base": null, + "href": "materialized-view://x:0/a", + "origin": "null", + "protocol": "materialized-view:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "android-app://x:0", + "base": null, + "href": "android-app://x:0", + "origin": "null", + "protocol": "android-app:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "chrome-distiller://x:0", + "base": null, + "href": "chrome-distiller://x:0", + "origin": "null", + "protocol": "chrome-distiller:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "chrome-extension://x:0", + "base": null, + "href": "chrome-extension://x:0", + "origin": "null", + "protocol": "chrome-extension:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "chrome-native://x:0", + "base": null, + "href": "chrome-native://x:0", + "origin": "null", + "protocol": "chrome-native:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "chrome-resource://x:0", + "base": null, + "href": "chrome-resource://x:0", + "origin": "null", + "protocol": "chrome-resource:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "chrome-search://x:0", + "base": null, + "href": "chrome-search://x:0", + "origin": "null", + "protocol": "chrome-search:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "fuchsia-dir://x:0", + "base": null, + "href": "fuchsia-dir://x:0", + "origin": "null", + "protocol": "fuchsia-dir:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "isolated-app://x:0", + "base": null, + "href": "isolated-app://x:0", + "origin": "null", + "protocol": "isolated-app:", + "username": "", + "password": "", + "host": "x:0", + "hostname": "x", + "port": "0", + "pathname": "", + "search": "", + "hash": "" + }, + "Scheme relative path starting with multiple slashes", + { + "input": "///test", + "base": "http://example.org/", + "href": "http://test/", + "protocol": "http:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///\\//\\//test", + "base": "http://example.org/", + "href": "http://test/", + "protocol": "http:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///example.org/path", + "base": "http://example.org/", + "href": "http://example.org/path", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + { + "input": "///example.org/../path", + "base": "http://example.org/", + "href": "http://example.org/path", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + { + "input": "///example.org/../../", + "base": "http://example.org/", + "href": "http://example.org/", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///example.org/../path/../../", + "base": "http://example.org/", + "href": "http://example.org/", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///example.org/../path/../../path", + "base": "http://example.org/", + "href": "http://example.org/path", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + { + "input": "/\\/\\//example.org/../path", + "base": "http://example.org/", + "href": "http://example.org/path", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + { + "input": "///abcdef/../", + "base": "file:///", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "/\\//\\/a/../", + "base": "file:///", + "href": "file://////", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "////", + "search": "", + "hash": "" + }, + { + "input": "//a/../", + "base": "file:///", + "href": "file://a/", + "protocol": "file:", + "username": "", + "password": "", + "host": "a", + "hostname": "a", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Non-special URL and backslashes", + { + "input": "non-special:\\\\opaque", + "base": null, + "href": "non-special:\\\\opaque", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\\\opaque", + "search": "", + "hash": "" + }, + { + "input": "non-special:\\\\opaque/path", + "base": null, + "href": "non-special:\\\\opaque/path", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\\\opaque/path", + "search": "", + "hash": "" + }, + { + "input": "non-special:\\\\opaque\\path", + "base": null, + "href": "non-special:\\\\opaque\\path", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\\\opaque\\path", + "search": "", + "hash": "" + }, + { + "input": "non-special:\\/opaque", + "base": null, + "href": "non-special:\\/opaque", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\/opaque", + "search": "", + "hash": "" + }, + { + "input": "non-special:/\\path", + "base": null, + "href": "non-special:/\\path", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/\\path", + "search": "", + "hash": "" + }, + { + "input": "non-special://host\\a", + "base": null, + "failure": true + }, + { + "input": "non-special://host/a\\b", + "base": null, + "href": "non-special://host/a\\b", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/a\\b", + "search": "", + "hash": "" + } +] diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.html b/Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.html new file mode 100644 index 00000000000..a3aa6ca187b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.html @@ -0,0 +1,15 @@ + + + + + + + +

+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.js b/Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.js new file mode 100644 index 00000000000..b4b639b813e --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-constructor.any.js @@ -0,0 +1,56 @@ +// META: script=/common/subset-tests-by-key.js +// META: timeout=long +// META: variant=?include=file +// META: variant=?include=javascript +// META: variant=?include=mailto +// META: variant=?exclude=(file|javascript|mailto) + +function runURLTests(urlTests) { + for (const expected of urlTests) { + // Skip comments + if (typeof expected === "string") + continue; + + const base = expected.base !== null ? expected.base : undefined; + + function getKey(expected) { + if (expected.protocol) { + return expected.protocol.replace(":", ""); + } + if (expected.failure) { + return expected.input.split(":")[0]; + } + return "other"; + } + + subsetTestByKey(getKey(expected), test, function() { + if (expected.failure) { + assert_throws_js(TypeError, function() { + new URL(expected.input, base); + }); + return; + } + + const url = new URL(expected.input, base); + assert_equals(url.href, expected.href, "href") + assert_equals(url.protocol, expected.protocol, "protocol") + assert_equals(url.username, expected.username, "username") + assert_equals(url.password, expected.password, "password") + assert_equals(url.host, expected.host, "host") + assert_equals(url.hostname, expected.hostname, "hostname") + assert_equals(url.port, expected.port, "port") + assert_equals(url.pathname, expected.pathname, "pathname") + assert_equals(url.search, expected.search, "search") + if ("searchParams" in expected) { + assert_true("searchParams" in url) + assert_equals(url.searchParams.toString(), expected.searchParams, "searchParams") + } + assert_equals(url.hash, expected.hash, "hash") + }, `Parsing: <${expected.input}> ${base ? "against <" + base + ">" : "without base"}`) + } +} + +promise_test(() => Promise.all([ + fetch("resources/urltestdata.json").then(res => res.json()), + fetch("resources/urltestdata-javascript-only.json").then(res => res.json()), +]).then((tests) => tests.flat()).then(runURLTests), "Loading data…"); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.html b/Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.html new file mode 100644 index 00000000000..8dec7f632da --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.js b/Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.js new file mode 100644 index 00000000000..b9e0c858531 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-origin.any.js @@ -0,0 +1,19 @@ +promise_test(() => Promise.all([ + fetch("resources/urltestdata.json").then(res => res.json()), + fetch("resources/urltestdata-javascript-only.json").then(res => res.json()), +]).then((tests) => tests.flat()).then(runURLTests), "Loading data…"); + +function runURLTests(urlTests) { + for (const expected of urlTests) { + // Skip comments and tests without "origin" expectation + if (typeof expected === "string" || !("origin" in expected)) + continue; + + const base = expected.base !== null ? expected.base : undefined; + + test(() => { + const url = new URL(expected.input, base); + assert_equals(url.origin, expected.origin, "origin"); + }, `Origin parsing: <${expected.input}> ${base ? "against <" + base + ">" : "without base"}`); + } +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.html b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.html new file mode 100644 index 00000000000..e66b24102f9 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.html @@ -0,0 +1,8 @@ + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.js b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.js new file mode 100644 index 00000000000..6a5e762cd42 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-a-area.window.js @@ -0,0 +1,43 @@ +// META: script=/common/subset-tests-by-key.js +// META: variant=?include=file +// META: variant=?include=javascript +// META: variant=?include=mailto +// META: variant=?exclude=(file|javascript|mailto) + +// Keep this file in sync with url-setters.any.js. + +promise_test(() => fetch("resources/setters_tests.json").then(res => res.json()).then(runURLSettersTests), "Loading data…"); + +function runURLSettersTests(all_test_cases) { + for (var attribute_to_be_set in all_test_cases) { + if (attribute_to_be_set == "comment") { + continue; + } + var test_cases = all_test_cases[attribute_to_be_set]; + for(var i = 0, l = test_cases.length; i < l; i++) { + var test_case = test_cases[i]; + var name = "Setting <" + test_case.href + ">." + attribute_to_be_set + + " = '" + test_case.new_value + "'"; + if ("comment" in test_case) { + name += " " + test_case.comment; + } + const key = test_case.href.split(":")[0]; + subsetTestByKey(key, test, function() { + var url = document.createElement("a"); + url.href = test_case.href; + url[attribute_to_be_set] = test_case.new_value; + for (var attribute in test_case.expected) { + assert_equals(url[attribute], test_case.expected[attribute]) + } + }, ": " + name) + subsetTestByKey(key, test, function() { + var url = document.createElement("area"); + url.href = test_case.href; + url[attribute_to_be_set] = test_case.new_value; + for (var attribute in test_case.expected) { + assert_equals(url[attribute], test_case.expected[attribute]) + } + }, ": " + name) + } + } +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.html b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.html new file mode 100644 index 00000000000..829121346f0 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.js b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.js new file mode 100644 index 00000000000..ac90cc17e0b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-setters-stripping.any.js @@ -0,0 +1,125 @@ +function urlString({ scheme = "https", + username = "username", + password = "password", + host = "host", + port = "8000", + pathname = "path", + search = "query", + hash = "fragment" }) { + return `${scheme}://${username}:${password}@${host}:${port}/${pathname}?${search}#${hash}`; +} + +function urlRecord(scheme) { + return new URL(urlString({ scheme })); +} + +for(const scheme of ["https", "wpt++"]) { + for(let i = 0; i < 0x20; i++) { + const stripped = i === 0x09 || i === 0x0A || i === 0x0D; + + // It turns out that user agents are surprisingly similar for these ranges so generate fewer + // tests. If this is changed also change the logic for host below. + if (i !== 0 && i !== 0x1F && !stripped) { + continue; + } + + const cpString = String.fromCodePoint(i); + const cpReference = "U+" + i.toString(16).toUpperCase().padStart(4, "0"); + + test(() => { + const expected = scheme === "https" ? (stripped ? "http" : "https") : (stripped ? "wpt--" : "wpt++"); + const url = urlRecord(scheme); + url.protocol = String.fromCodePoint(i) + (scheme === "https" ? "http" : "wpt--"); + assert_equals(url.protocol, expected + ":", "property"); + assert_equals(url.href, urlString({ scheme: expected }), "href"); + }, `Setting protocol with leading ${cpReference} (${scheme}:)`); + + test(() => { + const expected = scheme === "https" ? (stripped ? "http" : "https") : (stripped ? "wpt--" : "wpt++"); + const url = urlRecord(scheme); + url.protocol = (scheme === "https" ? "http" : "wpt--") + String.fromCodePoint(i); + assert_equals(url.protocol, expected + ":", "property"); + assert_equals(url.href, urlString({ scheme: expected }), "href"); + }, `Setting protocol with ${cpReference} before inserted colon (${scheme}:)`); + + // Cannot test protocol with trailing as the algorithm inserts a colon before proceeding + + // These do no stripping + for (const property of ["username", "password"]) { + for (const [type, expected, input] of [ + ["leading", encodeURIComponent(cpString) + "test", String.fromCodePoint(i) + "test"], + ["middle", "te" + encodeURIComponent(cpString) + "st", "te" + String.fromCodePoint(i) + "st"], + ["trailing", "test" + encodeURIComponent(cpString), "test" + String.fromCodePoint(i)] + ]) { + test(() => { + const url = urlRecord(scheme); + url[property] = input; + assert_equals(url[property], expected, "property"); + assert_equals(url.href, urlString({ scheme, [property]: expected }), "href"); + }, `Setting ${property} with ${type} ${cpReference} (${scheme}:)`); + } + } + + for (const [type, expectedPart, input] of [ + ["leading", (scheme === "https" ? cpString : encodeURIComponent(cpString)) + "test", String.fromCodePoint(i) + "test"], + ["middle", "te" + (scheme === "https" ? cpString : encodeURIComponent(cpString)) + "st", "te" + String.fromCodePoint(i) + "st"], + ["trailing", "test" + (scheme === "https" ? cpString : encodeURIComponent(cpString)), "test" + String.fromCodePoint(i)] + ]) { + test(() => { + const expected = i === 0x00 || (scheme === "https" && i === 0x1F) ? "host" : stripped ? "test" : expectedPart; + const url = urlRecord(scheme); + url.host = input; + assert_equals(url.host, expected + ":8000", "property"); + assert_equals(url.href, urlString({ scheme, host: expected }), "href"); + }, `Setting host with ${type} ${cpReference} (${scheme}:)`); + + test(() => { + const expected = i === 0x00 || (scheme === "https" && i === 0x1F) ? "host" : stripped ? "test" : expectedPart; + const url = urlRecord(scheme); + url.hostname = input; + assert_equals(url.hostname, expected, "property"); + assert_equals(url.href, urlString({ scheme, host: expected }), "href"); + }, `Setting hostname with ${type} ${cpReference} (${scheme}:)`); + } + + test(() => { + const expected = stripped ? "9000" : "8000"; + const url = urlRecord(scheme); + url.port = String.fromCodePoint(i) + "9000"; + assert_equals(url.port, expected, "property"); + assert_equals(url.href, urlString({ scheme, port: expected }), "href"); + }, `Setting port with leading ${cpReference} (${scheme}:)`); + + test(() => { + const expected = stripped ? "9000" : "90"; + const url = urlRecord(scheme); + url.port = "90" + String.fromCodePoint(i) + "00"; + assert_equals(url.port, expected, "property"); + assert_equals(url.href, urlString({ scheme, port: expected }), "href"); + }, `Setting port with middle ${cpReference} (${scheme}:)`); + + test(() => { + const expected = "9000"; + const url = urlRecord(scheme); + url.port = "9000" + String.fromCodePoint(i); + assert_equals(url.port, expected, "property"); + assert_equals(url.href, urlString({ scheme, port: expected }), "href"); + }, `Setting port with trailing ${cpReference} (${scheme}:)`); + + for (const [property, separator] of [["pathname", "/"], ["search", "?"], ["hash", "#"]]) { + for (const [type, expectedPart, input] of [ + ["leading", encodeURIComponent(cpString) + "test", String.fromCodePoint(i) + "test"], + ["middle", "te" + encodeURIComponent(cpString) + "st", "te" + String.fromCodePoint(i) + "st"], + ["trailing", "test" + encodeURIComponent(cpString), "test" + String.fromCodePoint(i)] + ]) { + test(() => { + const expected = stripped ? "test" : expectedPart; + const url = urlRecord(scheme); + url[property] = input; + assert_equals(url[property], separator + expected, "property"); + assert_equals(url.href, urlString({ scheme, [property]: expected }), "href"); + }, `Setting ${property} with ${type} ${cpReference} (${scheme}:)`); + } + } + } +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.html b/Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.html new file mode 100644 index 00000000000..b11bf605a21 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.js b/Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.js new file mode 100644 index 00000000000..fe88175ac63 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-setters.any.js @@ -0,0 +1,34 @@ +// META: script=/common/subset-tests-by-key.js +// META: variant=?include=file +// META: variant=?include=javascript +// META: variant=?include=mailto +// META: variant=?exclude=(file|javascript|mailto) + +// Keep this file in sync with url-setters-a-area.window.js. + +promise_test(() => fetch("resources/setters_tests.json").then(res => res.json()).then(runURLSettersTests), "Loading data…"); + +function runURLSettersTests(all_test_cases) { + for (var attribute_to_be_set in all_test_cases) { + if (attribute_to_be_set == "comment") { + continue; + } + var test_cases = all_test_cases[attribute_to_be_set]; + for(var i = 0, l = test_cases.length; i < l; i++) { + var test_case = test_cases[i]; + var name = "Setting <" + test_case.href + ">." + attribute_to_be_set + + " = '" + test_case.new_value + "'"; + if ("comment" in test_case) { + name += " " + test_case.comment; + } + const key = test_case.href.split(":")[0]; + subsetTestByKey(key, test, function() { + var url = new URL(test_case.href); + url[attribute_to_be_set] = test_case.new_value; + for (var attribute in test_case.expected) { + assert_equals(url[attribute], test_case.expected[attribute]) + } + }, "URL: " + name) + } + } +} diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.html b/Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.html new file mode 100644 index 00000000000..def07f6b2e2 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.js b/Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.js new file mode 100644 index 00000000000..0822e9da07a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-statics-parse.any.js @@ -0,0 +1,50 @@ +// This intentionally does not use resources/urltestdata.json to preserve resources. +[ + { + "url": undefined, + "base": undefined, + "expected": false + }, + { + "url": "aaa:b", + "base": undefined, + "expected": true + }, + { + "url": undefined, + "base": "aaa:b", + "expected": false + }, + { + "url": "aaa:/b", + "base": undefined, + "expected": true + }, + { + "url": undefined, + "base": "aaa:/b", + "expected": true + }, + { + "url": "https://test:test", + "base": undefined, + "expected": false + }, + { + "url": "a", + "base": "https://b/", + "expected": true + } +].forEach(({ url, base, expected }) => { + test(() => { + if (expected == false) { + assert_equals(URL.parse(url, base), null); + } else { + assert_equals(URL.parse(url, base).href, new URL(url, base).href); + } + }, `URL.parse(${url}, ${base})`); +}); + +test(() => { + assert_not_equals(URL.parse("https://example/"), URL.parse("https://example/")); +}, `URL.parse() should return a unique object`); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.html b/Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.html new file mode 100644 index 00000000000..ea765c71fed --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.js b/Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.js new file mode 100644 index 00000000000..65165f96c57 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/url-tojson.any.js @@ -0,0 +1,4 @@ +test(() => { + const a = new URL("https://example.com/") + assert_equals(JSON.stringify(a), "\"https://example.com/\"") +}) diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.html new file mode 100644 index 00000000000..266ead962db --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.js new file mode 100644 index 00000000000..847465cb921 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlencoded-parser.any.js @@ -0,0 +1,68 @@ +[ + { "input": "test", "output": [["test", ""]] }, + { "input": "\uFEFFtest=\uFEFF", "output": [["\uFEFFtest", "\uFEFF"]] }, + { "input": "%EF%BB%BFtest=%EF%BB%BF", "output": [["\uFEFFtest", "\uFEFF"]] }, + { "input": "%EF%BF%BF=%EF%BF%BF", "output": [["\uFFFF", "\uFFFF"]] }, + { "input": "%FE%FF", "output": [["\uFFFD\uFFFD", ""]] }, + { "input": "%FF%FE", "output": [["\uFFFD\uFFFD", ""]] }, + { "input": "†&†=x", "output": [["†", ""], ["†", "x"]] }, + { "input": "%C2", "output": [["\uFFFD", ""]] }, + { "input": "%C2x", "output": [["\uFFFDx", ""]] }, + { "input": "_charset_=windows-1252&test=%C2x", "output": [["_charset_", "windows-1252"], ["test", "\uFFFDx"]] }, + { "input": '', "output": [] }, + { "input": 'a', "output": [['a', '']] }, + { "input": 'a=b', "output": [['a', 'b']] }, + { "input": 'a=', "output": [['a', '']] }, + { "input": '=b', "output": [['', 'b']] }, + { "input": '&', "output": [] }, + { "input": '&a', "output": [['a', '']] }, + { "input": 'a&', "output": [['a', '']] }, + { "input": 'a&a', "output": [['a', ''], ['a', '']] }, + { "input": 'a&b&c', "output": [['a', ''], ['b', ''], ['c', '']] }, + { "input": 'a=b&c=d', "output": [['a', 'b'], ['c', 'd']] }, + { "input": 'a=b&c=d&', "output": [['a', 'b'], ['c', 'd']] }, + { "input": '&&&a=b&&&&c=d&', "output": [['a', 'b'], ['c', 'd']] }, + { "input": 'a=a&a=b&a=c', "output": [['a', 'a'], ['a', 'b'], ['a', 'c']] }, + { "input": 'a==a', "output": [['a', '=a']] }, + { "input": 'a=a+b+c+d', "output": [['a', 'a b c d']] }, + { "input": '%=a', "output": [['%', 'a']] }, + { "input": '%a=a', "output": [['%a', 'a']] }, + { "input": '%a_=a', "output": [['%a_', 'a']] }, + { "input": '%61=a', "output": [['a', 'a']] }, + { "input": '%61+%4d%4D=', "output": [['a MM', '']] }, + { "input": "id=0&value=%", "output": [['id', '0'], ['value', '%']] }, + { "input": "b=%2sf%2a", "output": [['b', '%2sf*']]}, + { "input": "b=%2%2af%2a", "output": [['b', '%2*f*']]}, + { "input": "b=%%2a", "output": [['b', '%*']]} +].forEach((val) => { + test(() => { + let sp = new URLSearchParams(val.input), + i = 0 + for (let item of sp) { + assert_array_equals(item, val.output[i]) + i++ + } + }, "URLSearchParams constructed with: " + val.input) + + promise_test(() => { + let init = new Request("about:blank", { body: val.input, method: "LADIDA", headers: {"Content-Type": "application/x-www-form-urlencoded;charset=windows-1252"} }).formData() + return init.then((fd) => { + let i = 0 + for (let item of fd) { + assert_array_equals(item, val.output[i]) + i++ + } + }) + }, "request.formData() with input: " + val.input) + + promise_test(() => { + let init = new Response(val.input, { headers: {"Content-Type": "application/x-www-form-urlencoded;charset=shift_jis"} }).formData() + return init.then((fd) => { + let i = 0 + for (let item of fd) { + assert_array_equals(item, val.output[i]) + i++ + } + }) + }, "response.formData() with input: " + val.input) +}); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.html new file mode 100644 index 00000000000..1ae878b422b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js new file mode 100644 index 00000000000..c597142c51d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js @@ -0,0 +1,83 @@ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + params.delete('a'); + assert_equals(params + '', 'c=d'); + params = new URLSearchParams('a=a&b=b&a=a&c=c'); + params.delete('a'); + assert_equals(params + '', 'b=b&c=c'); + params = new URLSearchParams('a=a&=&b=b&c=c'); + params.delete(''); + assert_equals(params + '', 'a=a&b=b&c=c'); + params = new URLSearchParams('a=a&null=null&b=b'); + params.delete(null); + assert_equals(params + '', 'a=a&b=b'); + params = new URLSearchParams('a=a&undefined=undefined&b=b'); + params.delete(undefined); + assert_equals(params + '', 'a=a&b=b'); +}, 'Delete basics'); + +test(function() { + var params = new URLSearchParams(); + params.append('first', 1); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"'); + params.delete('first'); + assert_false(params.has('first'), 'Search params object has no "first" name'); + params.append('first', 1); + params.append('first', 10); + params.delete('first'); + assert_false(params.has('first'), 'Search params object has no "first" name'); +}, 'Deleting appended multiple'); + +test(function() { + var url = new URL('http://example.com/?param1¶m2'); + url.searchParams.delete('param1'); + url.searchParams.delete('param2'); + assert_equals(url.href, 'http://example.com/', 'url.href does not have ?'); + assert_equals(url.search, '', 'url.search does not have ?'); +}, 'Deleting all params removes ? from URL'); + +test(function() { + var url = new URL('http://example.com/?'); + url.searchParams.delete('param1'); + assert_equals(url.href, 'http://example.com/', 'url.href does not have ?'); + assert_equals(url.search, '', 'url.search does not have ?'); +}, 'Removing non-existent param removes ? from URL'); + +test(() => { + const url = new URL('data:space ?test'); + assert_true(url.searchParams.has('test')); + url.searchParams.delete('test'); + assert_false(url.searchParams.has('test')); + assert_equals(url.search, ''); + assert_equals(url.pathname, 'space'); + assert_equals(url.href, 'data:space'); +}, 'Changing the query of a URL with an opaque path can impact the path'); + +test(() => { + const url = new URL('data:space ?test#test'); + url.searchParams.delete('test'); + assert_equals(url.search, ''); + assert_equals(url.pathname, 'space '); + assert_equals(url.href, 'data:space #test'); +}, 'Changing the query of a URL with an opaque path can impact the path if the URL has no fragment'); + +test(() => { + const params = new URLSearchParams(); + params.append('a', 'b'); + params.append('a', 'c'); + params.append('a', 'd'); + params.delete('a', 'c'); + assert_equals(params.toString(), 'a=b&a=d'); +}, "Two-argument delete()"); + +test(() => { + const params = new URLSearchParams(); + params.append('a', 'b'); + params.append('a', 'c'); + params.append('b', 'c'); + params.append('b', 'd'); + params.delete('b', 'c'); + params.delete('a', undefined); + assert_equals(params.toString(), 'b=d'); +}, "Two-argument delete() respects undefined as second arg"); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.html new file mode 100644 index 00000000000..2e4fead04e1 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.js new file mode 100644 index 00000000000..ff19643ac22 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-foreach.any.js @@ -0,0 +1,76 @@ +test(function() { + var params = new URLSearchParams('a=1&b=2&c=3'); + var keys = []; + var values = []; + params.forEach(function(value, key) { + keys.push(key); + values.push(value); + }); + assert_array_equals(keys, ['a', 'b', 'c']); + assert_array_equals(values, ['1', '2', '3']); +}, "ForEach Check"); + +test(function() { + let a = new URL("http://a.b/c?a=1&b=2&c=3&d=4"); + let b = a.searchParams; + var c = []; + for (const i of b) { + a.search = "x=1&y=2&z=3"; + c.push(i); + } + assert_array_equals(c[0], ["a","1"]); + assert_array_equals(c[1], ["y","2"]); + assert_array_equals(c[2], ["z","3"]); +}, "For-of Check"); + +test(function() { + let a = new URL("http://a.b/c"); + let b = a.searchParams; + for (const i of b) { + assert_unreached(i); + } +}, "empty"); + +test(function() { + const url = new URL("http://localhost/query?param0=0¶m1=1¶m2=2"); + const searchParams = url.searchParams; + const seen = []; + for (const param of searchParams) { + if (param[0] === 'param0') { + searchParams.delete('param1'); + } + seen.push(param); + } + + assert_array_equals(seen[0], ["param0", "0"]); + assert_array_equals(seen[1], ["param2", "2"]); +}, "delete next param during iteration"); + +test(function() { + const url = new URL("http://localhost/query?param0=0¶m1=1¶m2=2"); + const searchParams = url.searchParams; + const seen = []; + for (const param of searchParams) { + if (param[0] === 'param0') { + searchParams.delete('param0'); + // 'param1=1' is now in the first slot, so the next iteration will see 'param2=2'. + } else { + seen.push(param); + } + } + + assert_array_equals(seen[0], ["param2", "2"]); +}, "delete current param during iteration"); + +test(function() { + const url = new URL("http://localhost/query?param0=0¶m1=1¶m2=2"); + const searchParams = url.searchParams; + const seen = []; + for (const param of searchParams) { + seen.push(param[0]); + searchParams.delete(param[0]); + } + + assert_array_equals(seen, ["param0", "param2"], "param1 should not have been seen by the loop"); + assert_equals(String(searchParams), "param1=1", "param1 should remain"); +}, "delete every param seen during iteration"); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.html new file mode 100644 index 00000000000..ac7b1026ab0 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.js new file mode 100644 index 00000000000..a2610fc933a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-get.any.js @@ -0,0 +1,21 @@ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + assert_equals(params.get('a'), 'b'); + assert_equals(params.get('c'), 'd'); + assert_equals(params.get('e'), null); + params = new URLSearchParams('a=b&c=d&a=e'); + assert_equals(params.get('a'), 'b'); + params = new URLSearchParams('=b&c=d'); + assert_equals(params.get(''), 'b'); + params = new URLSearchParams('a=&c=d&a=e'); + assert_equals(params.get('a'), ''); +}, 'Get basics'); + +test(function() { + var params = new URLSearchParams('first=second&third&&'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_equals(params.get('first'), 'second', 'Search params object has name "first" with value "second"'); + assert_equals(params.get('third'), '', 'Search params object has name "third" with the empty value.'); + assert_equals(params.get('fourth'), null, 'Search params object has no "fourth" name and value.'); +}, 'More get() basics'); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.html new file mode 100644 index 00000000000..04bfdb3627a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.js new file mode 100644 index 00000000000..5d1a35352ac --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-getall.any.js @@ -0,0 +1,25 @@ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + assert_array_equals(params.getAll('a'), ['b']); + assert_array_equals(params.getAll('c'), ['d']); + assert_array_equals(params.getAll('e'), []); + params = new URLSearchParams('a=b&c=d&a=e'); + assert_array_equals(params.getAll('a'), ['b', 'e']); + params = new URLSearchParams('=b&c=d'); + assert_array_equals(params.getAll(''), ['b']); + params = new URLSearchParams('a=&c=d&a=e'); + assert_array_equals(params.getAll('a'), ['', 'e']); +}, 'getAll() basics'); + +test(function() { + var params = new URLSearchParams('a=1&a=2&a=3&a'); + assert_true(params.has('a'), 'Search params object has name "a"'); + var matches = params.getAll('a'); + assert_true(matches && matches.length == 4, 'Search params object has values for name "a"'); + assert_array_equals(matches, ['1', '2', '3', ''], 'Search params object has expected name "a" values'); + params.set('a', 'one'); + assert_equals(params.get('a'), 'one', 'Search params object has name "a" with value "one"'); + var matches = params.getAll('a'); + assert_true(matches && matches.length == 1, 'Search params object has values for name "a"'); + assert_array_equals(matches, ['one'], 'Search params object has expected name "a" values'); +}, 'getAll() multiples'); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.html new file mode 100644 index 00000000000..7b57382397d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.js new file mode 100644 index 00000000000..2133a5da2f8 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-has.any.js @@ -0,0 +1,45 @@ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + assert_true(params.has('a')); + assert_true(params.has('c')); + assert_false(params.has('e')); + params = new URLSearchParams('a=b&c=d&a=e'); + assert_true(params.has('a')); + params = new URLSearchParams('=b&c=d'); + assert_true(params.has('')); + params = new URLSearchParams('null=a'); + assert_true(params.has(null)); +}, 'Has basics'); + +test(function() { + var params = new URLSearchParams('a=b&c=d&&'); + params.append('first', 1); + params.append('first', 2); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_true(params.has('c'), 'Search params object has name "c"'); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_false(params.has('d'), 'Search params object has no name "d"'); + params.delete('first'); + assert_false(params.has('first'), 'Search params object has no name "first"'); +}, 'has() following delete()'); + +test(() => { + const params = new URLSearchParams("a=b&a=d&c&e&"); + assert_true(params.has('a', 'b')); + assert_false(params.has('a', 'c')); + assert_true(params.has('a', 'd')); + assert_true(params.has('e', '')); + params.append('first', null); + assert_false(params.has('first', '')); + assert_true(params.has('first', 'null')); + params.delete('a', 'b'); + assert_true(params.has('a', 'd')); +}, "Two-argument has()"); + +test(() => { + const params = new URLSearchParams("a=b&a=d&c&e&"); + assert_true(params.has('a', 'b')); + assert_false(params.has('a', 'c')); + assert_true(params.has('a', 'd')); + assert_true(params.has('a', undefined)); +}, "Two-argument has() respects undefined as second arg"); \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.html new file mode 100644 index 00000000000..4d86cd5c336 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.js new file mode 100644 index 00000000000..eb24cac87b6 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-set.any.js @@ -0,0 +1,22 @@ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + params.set('a', 'B'); + assert_equals(params + '', 'a=B&c=d'); + params = new URLSearchParams('a=b&c=d&a=e'); + params.set('a', 'B'); + assert_equals(params + '', 'a=B&c=d') + params.set('e', 'f'); + assert_equals(params + '', 'a=B&c=d&e=f') +}, 'Set basics'); + +test(function() { + var params = new URLSearchParams('a=1&a=2&a=3'); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_equals(params.get('a'), '1', 'Search params object has name "a" with value "1"'); + params.set('first', 4); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_equals(params.get('a'), '1', 'Search params object has name "a" with value "1"'); + params.set('a', 4); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_equals(params.get('a'), '4', 'Search params object has name "a" with value "4"'); +}, 'URLSearchParams.set'); diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.html new file mode 100644 index 00000000000..a01e22b46de --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.js new file mode 100644 index 00000000000..4fd8cef6923 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-sort.any.js @@ -0,0 +1,62 @@ +[ + { + "input": "z=b&a=b&z=a&a=a", + "output": [["a", "b"], ["a", "a"], ["z", "b"], ["z", "a"]] + }, + { + "input": "\uFFFD=x&\uFFFC&\uFFFD=a", + "output": [["\uFFFC", ""], ["\uFFFD", "x"], ["\uFFFD", "a"]] + }, + { + "input": "ffi&🌈", // 🌈 > code point, but < code unit because two code units + "output": [["🌈", ""], ["ffi", ""]] + }, + { + "input": "é&e\uFFFD&e\u0301", + "output": [["e\u0301", ""], ["e\uFFFD", ""], ["é", ""]] + }, + { + "input": "z=z&a=a&z=y&a=b&z=x&a=c&z=w&a=d&z=v&a=e&z=u&a=f&z=t&a=g", + "output": [["a", "a"], ["a", "b"], ["a", "c"], ["a", "d"], ["a", "e"], ["a", "f"], ["a", "g"], ["z", "z"], ["z", "y"], ["z", "x"], ["z", "w"], ["z", "v"], ["z", "u"], ["z", "t"]] + }, + { + "input": "bbb&bb&aaa&aa=x&aa=y", + "output": [["aa", "x"], ["aa", "y"], ["aaa", ""], ["bb", ""], ["bbb", ""]] + }, + { + "input": "z=z&=f&=t&=x", + "output": [["", "f"], ["", "t"], ["", "x"], ["z", "z"]] + }, + { + "input": "a🌈&a💩", + "output": [["a🌈", ""], ["a💩", ""]] + } +].forEach((val) => { + test(() => { + let params = new URLSearchParams(val.input), + i = 0 + params.sort() + for(let param of params) { + assert_array_equals(param, val.output[i]) + i++ + } + }, "Parse and sort: " + val.input) + + test(() => { + let url = new URL("?" + val.input, "https://example/") + url.searchParams.sort() + let params = new URLSearchParams(url.search), + i = 0 + for(let param of params) { + assert_array_equals(param, val.output[i]) + i++ + } + }, "URL parse and sort: " + val.input) +}) + +test(function() { + const url = new URL("http://example.com/?") + url.searchParams.sort() + assert_equals(url.href, "http://example.com/") + assert_equals(url.search, "") +}, "Sorting non-existent params removes ? from URL") diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.html b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.html new file mode 100644 index 00000000000..7241e96d494 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.js new file mode 100644 index 00000000000..6187db64b17 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-stringifier.any.js @@ -0,0 +1,145 @@ +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b c'); + assert_equals(params + '', 'a=b+c'); + params.delete('a'); + params.append('a b', 'c'); + assert_equals(params + '', 'a+b=c'); +}, 'Serialize space'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', ''); + assert_equals(params + '', 'a='); + params.append('a', ''); + assert_equals(params + '', 'a=&a='); + params.append('', 'b'); + assert_equals(params + '', 'a=&a=&=b'); + params.append('', ''); + assert_equals(params + '', 'a=&a=&=b&='); + params.append('', ''); + assert_equals(params + '', 'a=&a=&=b&=&='); +}, 'Serialize empty value'); + +test(function() { + var params = new URLSearchParams(); + params.append('', 'b'); + assert_equals(params + '', '=b'); + params.append('', 'b'); + assert_equals(params + '', '=b&=b'); +}, 'Serialize empty name'); + +test(function() { + var params = new URLSearchParams(); + params.append('', ''); + assert_equals(params + '', '='); + params.append('', ''); + assert_equals(params + '', '=&='); +}, 'Serialize empty name and value'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b+c'); + assert_equals(params + '', 'a=b%2Bc'); + params.delete('a'); + params.append('a+b', 'c'); + assert_equals(params + '', 'a%2Bb=c'); +}, 'Serialize +'); + +test(function() { + var params = new URLSearchParams(); + params.append('=', 'a'); + assert_equals(params + '', '%3D=a'); + params.append('b', '='); + assert_equals(params + '', '%3D=a&b=%3D'); +}, 'Serialize ='); + +test(function() { + var params = new URLSearchParams(); + params.append('&', 'a'); + assert_equals(params + '', '%26=a'); + params.append('b', '&'); + assert_equals(params + '', '%26=a&b=%26'); +}, 'Serialize &'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', '*-._'); + assert_equals(params + '', 'a=*-._'); + params.delete('a'); + params.append('*-._', 'c'); + assert_equals(params + '', '*-._=c'); +}, 'Serialize *-._'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b%c'); + assert_equals(params + '', 'a=b%25c'); + params.delete('a'); + params.append('a%b', 'c'); + assert_equals(params + '', 'a%25b=c'); + + params = new URLSearchParams('id=0&value=%') + assert_equals(params + '', 'id=0&value=%25') +}, 'Serialize %'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b\0c'); + assert_equals(params + '', 'a=b%00c'); + params.delete('a'); + params.append('a\0b', 'c'); + assert_equals(params + '', 'a%00b=c'); +}, 'Serialize \\0'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b\uD83D\uDCA9c'); + assert_equals(params + '', 'a=b%F0%9F%92%A9c'); + params.delete('a'); + params.append('a\uD83D\uDCA9b', 'c'); + assert_equals(params + '', 'a%F0%9F%92%A9b=c'); +}, 'Serialize \uD83D\uDCA9'); // Unicode Character 'PILE OF POO' (U+1F4A9) + +test(function() { + var params; + params = new URLSearchParams('a=b&c=d&&e&&'); + assert_equals(params.toString(), 'a=b&c=d&e='); + params = new URLSearchParams('a = b &a=b&c=d%20'); + assert_equals(params.toString(), 'a+=+b+&a=b&c=d+'); + // The lone '=' _does_ survive the roundtrip. + params = new URLSearchParams('a=&a=b'); + assert_equals(params.toString(), 'a=&a=b'); + + params = new URLSearchParams('b=%2sf%2a'); + assert_equals(params.toString(), 'b=%252sf*'); + + params = new URLSearchParams('b=%2%2af%2a'); + assert_equals(params.toString(), 'b=%252*f*'); + + params = new URLSearchParams('b=%%2a'); + assert_equals(params.toString(), 'b=%25*'); +}, 'URLSearchParams.toString'); + +test(() => { + const url = new URL('http://www.example.com/?a=b,c'); + const params = url.searchParams; + + assert_equals(url.toString(), 'http://www.example.com/?a=b,c'); + assert_equals(params.toString(), 'a=b%2Cc'); + + params.append('x', 'y'); + + assert_equals(url.toString(), 'http://www.example.com/?a=b%2Cc&x=y'); + assert_equals(params.toString(), 'a=b%2Cc&x=y'); +}, 'URLSearchParams connected to URL'); + +test(() => { + const url = new URL('http://www.example.com/'); + const params = url.searchParams; + + params.append('a\nb', 'c\rd'); + params.append('e\n\rf', 'g\r\nh'); + + assert_equals(params.toString(), "a%0Ab=c%0Dd&e%0A%0Df=g%0D%0Ah"); +}, 'URLSearchParams must not do newline normalization'); From 01d1a9528b99a7bcd21b181a0b537addc8afd144 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 17 Mar 2025 16:12:50 +0000 Subject: [PATCH 016/141] LibWeb: Calculate the correct resolved value for inset properties This improves the output of `getComputedStyle()` for the `top`, `bottom`, `left` and `right` properties, where the used value is now returned rather than the computed value, where applicable." --- .../CSS/ResolvedCSSStyleDeclaration.cpp | 51 ++- .../getComputedStyle-insets-absolute.txt | 330 +++++++++++++++ .../getComputedStyle-insets-relative.txt | 258 ++++++++++++ .../getComputedStyle-insets-absolute.html | 21 + .../getComputedStyle-insets-relative.html | 19 + .../cssom/support/getComputedStyle-insets.js | 375 ++++++++++++++++++ 6 files changed, 1045 insertions(+), 9 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-absolute.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-relative.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-absolute.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-relative.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/cssom/support/getComputedStyle-insets.js diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index c3a0f306197..ba618a9f4c7 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -180,6 +180,20 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return {}; }; + auto used_value_for_inset = [&layout_node, used_value_for_property](LengthPercentage const& start_side, LengthPercentage const& end_side, Function&& used_value_getter) -> Optional { + if (!layout_node.is_positioned()) + return {}; + + // FIXME: Support getting the used value when position is sticky. + if (layout_node.is_sticky_position()) + return {}; + + if (!start_side.is_percentage() && !start_side.is_calculated() && !start_side.is_auto() && !end_side.is_auto()) + return {}; + + return used_value_for_property(move(used_value_getter)); + }; + auto get_computed_value = [this](PropertyID property_id) -> auto const& { if (m_pseudo_element.has_value()) return m_element->pseudo_element_computed_properties(m_pseudo_element.value())->property(property_id); @@ -376,11 +390,16 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // -> right // -> top // -> A resolved value special case property like top defined in another specification - // FIXME: If the property applies to a positioned element and the resolved value of the display property is not + // If the property applies to a positioned element and the resolved value of the display property is not // none or contents, and the property is not over-constrained, then the resolved value is the used value. // Otherwise the resolved value is the computed value. - case PropertyID::Bottom: - return style_value_for_length_percentage(layout_node.computed_values().inset().bottom()); + case PropertyID::Bottom: { + auto& inset = layout_node.computed_values().inset(); + if (auto maybe_used_value = used_value_for_inset(inset.bottom(), inset.top(), [](auto const& paintable_box) { return paintable_box.box_model().inset.bottom; }); maybe_used_value.has_value()) + return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); + + return style_value_for_length_percentage(inset.bottom()); + } case PropertyID::InsetBlockEnd: return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::BlockEnd); case PropertyID::InsetBlockStart: @@ -389,12 +408,26 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::InlineEnd); case PropertyID::InsetInlineStart: return style_value_for_length_box_logical_side(layout_node, layout_node.computed_values().inset(), LogicalSide::InlineStart); - case PropertyID::Left: - return style_value_for_length_percentage(layout_node.computed_values().inset().left()); - case PropertyID::Right: - return style_value_for_length_percentage(layout_node.computed_values().inset().right()); - case PropertyID::Top: - return style_value_for_length_percentage(layout_node.computed_values().inset().top()); + case PropertyID::Left: { + auto& inset = layout_node.computed_values().inset(); + if (auto maybe_used_value = used_value_for_inset(inset.left(), inset.right(), [](auto const& paintable_box) { return paintable_box.box_model().inset.left; }); maybe_used_value.has_value()) + return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); + return style_value_for_length_percentage(inset.left()); + } + case PropertyID::Right: { + auto& inset = layout_node.computed_values().inset(); + if (auto maybe_used_value = used_value_for_inset(inset.right(), inset.left(), [](auto const& paintable_box) { return paintable_box.box_model().inset.right; }); maybe_used_value.has_value()) + return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); + + return style_value_for_length_percentage(inset.right()); + } + case PropertyID::Top: { + auto& inset = layout_node.computed_values().inset(); + if (auto maybe_used_value = used_value_for_inset(inset.top(), inset.bottom(), [](auto const& paintable_box) { return paintable_box.box_model().inset.top; }); maybe_used_value.has_value()) + return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); + + return style_value_for_length_percentage(inset.top()); + } // -> A resolved value special case property defined in another specification // As defined in the relevant specification. diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-absolute.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-absolute.txt new file mode 100644 index 00000000000..e2fef9591dc --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-absolute.txt @@ -0,0 +1,330 @@ +Harness status: OK + +Found 324 tests + +288 Pass +36 Fail +Pass horizontal-tb ltr inside horizontal-tb ltr - Pixels resolve as-is +Pass horizontal-tb ltr inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb ltr - Percentages are absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb ltr - calc() is absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb ltr - Pixels resolve as-is when overconstrained +Pass horizontal-tb ltr inside horizontal-tb ltr - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb ltr inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb ltr inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside horizontal-tb rtl - Pixels resolve as-is +Pass horizontal-tb ltr inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb rtl - Percentages are absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb rtl - calc() is absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb rtl - Pixels resolve as-is when overconstrained +Pass horizontal-tb ltr inside horizontal-tb rtl - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb ltr inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb ltr inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-lr ltr - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-lr ltr - Relative lengths are absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr ltr - Percentages are absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr ltr - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr ltr - Pixels resolve as-is when overconstrained +Pass horizontal-tb ltr inside vertical-lr ltr - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb ltr inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb ltr inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-lr rtl - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-lr rtl - Relative lengths are absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr rtl - Percentages are absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr rtl - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr rtl - Pixels resolve as-is when overconstrained +Pass horizontal-tb ltr inside vertical-lr rtl - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb ltr inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb ltr inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-rl ltr - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-rl ltr - Relative lengths are absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl ltr - Percentages are absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl ltr - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl ltr - Pixels resolve as-is when overconstrained +Pass horizontal-tb ltr inside vertical-rl ltr - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb ltr inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb ltr inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-rl rtl - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-rl rtl - Relative lengths are absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl rtl - Percentages are absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl rtl - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl rtl - Pixels resolve as-is when overconstrained +Pass horizontal-tb ltr inside vertical-rl rtl - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb ltr inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb ltr inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside horizontal-tb ltr - Pixels resolve as-is +Pass horizontal-tb rtl inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb ltr - Percentages are absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb ltr - calc() is absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb ltr - Pixels resolve as-is when overconstrained +Pass horizontal-tb rtl inside horizontal-tb ltr - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb rtl inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb rtl inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside horizontal-tb rtl - Pixels resolve as-is +Pass horizontal-tb rtl inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb rtl - Percentages are absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb rtl - calc() is absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb rtl - Pixels resolve as-is when overconstrained +Pass horizontal-tb rtl inside horizontal-tb rtl - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb rtl inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb rtl inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-lr ltr - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-lr ltr - Relative lengths are absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr ltr - Percentages are absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr ltr - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr ltr - Pixels resolve as-is when overconstrained +Pass horizontal-tb rtl inside vertical-lr ltr - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb rtl inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb rtl inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-lr rtl - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-lr rtl - Relative lengths are absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr rtl - Percentages are absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr rtl - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr rtl - Pixels resolve as-is when overconstrained +Pass horizontal-tb rtl inside vertical-lr rtl - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb rtl inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb rtl inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-rl ltr - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-rl ltr - Relative lengths are absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl ltr - Percentages are absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl ltr - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl ltr - Pixels resolve as-is when overconstrained +Pass horizontal-tb rtl inside vertical-rl ltr - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb rtl inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb rtl inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-rl rtl - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-rl rtl - Relative lengths are absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl rtl - Percentages are absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl rtl - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl rtl - Pixels resolve as-is when overconstrained +Pass horizontal-tb rtl inside vertical-rl rtl - Percentages absolutize the computed value when overconstrained +Pass horizontal-tb rtl inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail horizontal-tb rtl inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-lr ltr inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb ltr - Percentages are absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb ltr - Pixels resolve as-is when overconstrained +Pass vertical-lr ltr inside horizontal-tb ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-lr ltr inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr ltr inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-lr ltr inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb rtl - Percentages are absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb rtl - Pixels resolve as-is when overconstrained +Pass vertical-lr ltr inside horizontal-tb rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-lr ltr inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr ltr inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-lr ltr - Relative lengths are absolutized into pixels +Pass vertical-lr ltr inside vertical-lr ltr - Percentages are absolutized into pixels +Pass vertical-lr ltr inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-lr ltr - Pixels resolve as-is when overconstrained +Pass vertical-lr ltr inside vertical-lr ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-lr ltr inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr ltr inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-lr rtl - Relative lengths are absolutized into pixels +Pass vertical-lr ltr inside vertical-lr rtl - Percentages are absolutized into pixels +Pass vertical-lr ltr inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-lr rtl - Pixels resolve as-is when overconstrained +Pass vertical-lr ltr inside vertical-lr rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-lr ltr inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr ltr inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-rl ltr - Relative lengths are absolutized into pixels +Pass vertical-lr ltr inside vertical-rl ltr - Percentages are absolutized into pixels +Pass vertical-lr ltr inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-rl ltr - Pixels resolve as-is when overconstrained +Pass vertical-lr ltr inside vertical-rl ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-lr ltr inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr ltr inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-rl rtl - Relative lengths are absolutized into pixels +Pass vertical-lr ltr inside vertical-rl rtl - Percentages are absolutized into pixels +Pass vertical-lr ltr inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-rl rtl - Pixels resolve as-is when overconstrained +Pass vertical-lr ltr inside vertical-rl rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-lr ltr inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr ltr inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-lr rtl inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb ltr - Percentages are absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb ltr - Pixels resolve as-is when overconstrained +Pass vertical-lr rtl inside horizontal-tb ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-lr rtl inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr rtl inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-lr rtl inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb rtl - Percentages are absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb rtl - Pixels resolve as-is when overconstrained +Pass vertical-lr rtl inside horizontal-tb rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-lr rtl inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr rtl inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-lr ltr - Relative lengths are absolutized into pixels +Pass vertical-lr rtl inside vertical-lr ltr - Percentages are absolutized into pixels +Pass vertical-lr rtl inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-lr ltr - Pixels resolve as-is when overconstrained +Pass vertical-lr rtl inside vertical-lr ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-lr rtl inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr rtl inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-lr rtl - Relative lengths are absolutized into pixels +Pass vertical-lr rtl inside vertical-lr rtl - Percentages are absolutized into pixels +Pass vertical-lr rtl inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-lr rtl - Pixels resolve as-is when overconstrained +Pass vertical-lr rtl inside vertical-lr rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-lr rtl inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr rtl inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-rl ltr - Relative lengths are absolutized into pixels +Pass vertical-lr rtl inside vertical-rl ltr - Percentages are absolutized into pixels +Pass vertical-lr rtl inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-rl ltr - Pixels resolve as-is when overconstrained +Pass vertical-lr rtl inside vertical-rl ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-lr rtl inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr rtl inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-rl rtl - Relative lengths are absolutized into pixels +Pass vertical-lr rtl inside vertical-rl rtl - Percentages are absolutized into pixels +Pass vertical-lr rtl inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-rl rtl - Pixels resolve as-is when overconstrained +Pass vertical-lr rtl inside vertical-rl rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-lr rtl inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-lr rtl inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-rl ltr inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb ltr - Percentages are absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb ltr - Pixels resolve as-is when overconstrained +Pass vertical-rl ltr inside horizontal-tb ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-rl ltr inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl ltr inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-rl ltr inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb rtl - Percentages are absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb rtl - Pixels resolve as-is when overconstrained +Pass vertical-rl ltr inside horizontal-tb rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-rl ltr inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl ltr inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-lr ltr - Relative lengths are absolutized into pixels +Pass vertical-rl ltr inside vertical-lr ltr - Percentages are absolutized into pixels +Pass vertical-rl ltr inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-lr ltr - Pixels resolve as-is when overconstrained +Pass vertical-rl ltr inside vertical-lr ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-rl ltr inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl ltr inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-lr rtl - Relative lengths are absolutized into pixels +Pass vertical-rl ltr inside vertical-lr rtl - Percentages are absolutized into pixels +Pass vertical-rl ltr inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-lr rtl - Pixels resolve as-is when overconstrained +Pass vertical-rl ltr inside vertical-lr rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-rl ltr inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl ltr inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-rl ltr - Relative lengths are absolutized into pixels +Pass vertical-rl ltr inside vertical-rl ltr - Percentages are absolutized into pixels +Pass vertical-rl ltr inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-rl ltr - Pixels resolve as-is when overconstrained +Pass vertical-rl ltr inside vertical-rl ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-rl ltr inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl ltr inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-rl rtl - Relative lengths are absolutized into pixels +Pass vertical-rl ltr inside vertical-rl rtl - Percentages are absolutized into pixels +Pass vertical-rl ltr inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-rl rtl - Pixels resolve as-is when overconstrained +Pass vertical-rl ltr inside vertical-rl rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-rl ltr inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl ltr inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-rl rtl inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb ltr - Percentages are absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb ltr - Pixels resolve as-is when overconstrained +Pass vertical-rl rtl inside horizontal-tb ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-rl rtl inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl rtl inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-rl rtl inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb rtl - Percentages are absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb rtl - Pixels resolve as-is when overconstrained +Pass vertical-rl rtl inside horizontal-tb rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-rl rtl inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl rtl inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-lr ltr - Relative lengths are absolutized into pixels +Pass vertical-rl rtl inside vertical-lr ltr - Percentages are absolutized into pixels +Pass vertical-rl rtl inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-lr ltr - Pixels resolve as-is when overconstrained +Pass vertical-rl rtl inside vertical-lr ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-rl rtl inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl rtl inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-lr rtl - Relative lengths are absolutized into pixels +Pass vertical-rl rtl inside vertical-lr rtl - Percentages are absolutized into pixels +Pass vertical-rl rtl inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-lr rtl - Pixels resolve as-is when overconstrained +Pass vertical-rl rtl inside vertical-lr rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-rl rtl inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl rtl inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-rl ltr - Relative lengths are absolutized into pixels +Pass vertical-rl rtl inside vertical-rl ltr - Percentages are absolutized into pixels +Pass vertical-rl rtl inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-rl ltr - Pixels resolve as-is when overconstrained +Pass vertical-rl rtl inside vertical-rl ltr - Percentages absolutize the computed value when overconstrained +Pass vertical-rl rtl inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl rtl inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-rl rtl - Relative lengths are absolutized into pixels +Pass vertical-rl rtl inside vertical-rl rtl - Percentages are absolutized into pixels +Pass vertical-rl rtl inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-rl rtl - Pixels resolve as-is when overconstrained +Pass vertical-rl rtl inside vertical-rl rtl - Percentages absolutize the computed value when overconstrained +Pass vertical-rl rtl inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Fail vertical-rl rtl inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-relative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-relative.txt new file mode 100644 index 00000000000..f5b3900430c --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/getComputedStyle-insets-relative.txt @@ -0,0 +1,258 @@ +Harness status: OK + +Found 252 tests + +180 Pass +72 Fail +Pass horizontal-tb ltr inside horizontal-tb ltr - Pixels resolve as-is +Pass horizontal-tb ltr inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Fail horizontal-tb ltr inside horizontal-tb ltr - Percentages are absolutized into pixels +Fail horizontal-tb ltr inside horizontal-tb ltr - calc() is absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside horizontal-tb rtl - Pixels resolve as-is +Pass horizontal-tb ltr inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Fail horizontal-tb ltr inside horizontal-tb rtl - Percentages are absolutized into pixels +Fail horizontal-tb ltr inside horizontal-tb rtl - calc() is absolutized into pixels +Pass horizontal-tb ltr inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-lr ltr - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-lr ltr - Relative lengths are absolutized into pixels +Fail horizontal-tb ltr inside vertical-lr ltr - Percentages are absolutized into pixels +Fail horizontal-tb ltr inside vertical-lr ltr - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-lr rtl - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-lr rtl - Relative lengths are absolutized into pixels +Fail horizontal-tb ltr inside vertical-lr rtl - Percentages are absolutized into pixels +Fail horizontal-tb ltr inside vertical-lr rtl - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-rl ltr - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-rl ltr - Relative lengths are absolutized into pixels +Fail horizontal-tb ltr inside vertical-rl ltr - Percentages are absolutized into pixels +Fail horizontal-tb ltr inside vertical-rl ltr - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb ltr inside vertical-rl rtl - Pixels resolve as-is +Pass horizontal-tb ltr inside vertical-rl rtl - Relative lengths are absolutized into pixels +Fail horizontal-tb ltr inside vertical-rl rtl - Percentages are absolutized into pixels +Fail horizontal-tb ltr inside vertical-rl rtl - calc() is absolutized into pixels +Pass horizontal-tb ltr inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb ltr inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside horizontal-tb ltr - Pixels resolve as-is +Pass horizontal-tb rtl inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Fail horizontal-tb rtl inside horizontal-tb ltr - Percentages are absolutized into pixels +Fail horizontal-tb rtl inside horizontal-tb ltr - calc() is absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside horizontal-tb rtl - Pixels resolve as-is +Pass horizontal-tb rtl inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Fail horizontal-tb rtl inside horizontal-tb rtl - Percentages are absolutized into pixels +Fail horizontal-tb rtl inside horizontal-tb rtl - calc() is absolutized into pixels +Pass horizontal-tb rtl inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-lr ltr - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-lr ltr - Relative lengths are absolutized into pixels +Fail horizontal-tb rtl inside vertical-lr ltr - Percentages are absolutized into pixels +Fail horizontal-tb rtl inside vertical-lr ltr - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-lr rtl - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-lr rtl - Relative lengths are absolutized into pixels +Fail horizontal-tb rtl inside vertical-lr rtl - Percentages are absolutized into pixels +Fail horizontal-tb rtl inside vertical-lr rtl - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-rl ltr - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-rl ltr - Relative lengths are absolutized into pixels +Fail horizontal-tb rtl inside vertical-rl ltr - Percentages are absolutized into pixels +Fail horizontal-tb rtl inside vertical-rl ltr - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass horizontal-tb rtl inside vertical-rl rtl - Pixels resolve as-is +Pass horizontal-tb rtl inside vertical-rl rtl - Relative lengths are absolutized into pixels +Fail horizontal-tb rtl inside vertical-rl rtl - Percentages are absolutized into pixels +Fail horizontal-tb rtl inside vertical-rl rtl - calc() is absolutized into pixels +Pass horizontal-tb rtl inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass horizontal-tb rtl inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-lr ltr inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Fail vertical-lr ltr inside horizontal-tb ltr - Percentages are absolutized into pixels +Fail vertical-lr ltr inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-lr ltr inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Fail vertical-lr ltr inside horizontal-tb rtl - Percentages are absolutized into pixels +Fail vertical-lr ltr inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-lr ltr inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-lr ltr - Relative lengths are absolutized into pixels +Fail vertical-lr ltr inside vertical-lr ltr - Percentages are absolutized into pixels +Fail vertical-lr ltr inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-lr rtl - Relative lengths are absolutized into pixels +Fail vertical-lr ltr inside vertical-lr rtl - Percentages are absolutized into pixels +Fail vertical-lr ltr inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-rl ltr - Relative lengths are absolutized into pixels +Fail vertical-lr ltr inside vertical-rl ltr - Percentages are absolutized into pixels +Fail vertical-lr ltr inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr ltr inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-lr ltr inside vertical-rl rtl - Relative lengths are absolutized into pixels +Fail vertical-lr ltr inside vertical-rl rtl - Percentages are absolutized into pixels +Fail vertical-lr ltr inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-lr ltr inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr ltr inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-lr rtl inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Fail vertical-lr rtl inside horizontal-tb ltr - Percentages are absolutized into pixels +Fail vertical-lr rtl inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-lr rtl inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Fail vertical-lr rtl inside horizontal-tb rtl - Percentages are absolutized into pixels +Fail vertical-lr rtl inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-lr rtl inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-lr ltr - Relative lengths are absolutized into pixels +Fail vertical-lr rtl inside vertical-lr ltr - Percentages are absolutized into pixels +Fail vertical-lr rtl inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-lr rtl - Relative lengths are absolutized into pixels +Fail vertical-lr rtl inside vertical-lr rtl - Percentages are absolutized into pixels +Fail vertical-lr rtl inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-rl ltr - Relative lengths are absolutized into pixels +Fail vertical-lr rtl inside vertical-rl ltr - Percentages are absolutized into pixels +Fail vertical-lr rtl inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-lr rtl inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-lr rtl inside vertical-rl rtl - Relative lengths are absolutized into pixels +Fail vertical-lr rtl inside vertical-rl rtl - Percentages are absolutized into pixels +Fail vertical-lr rtl inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-lr rtl inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-lr rtl inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-rl ltr inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Fail vertical-rl ltr inside horizontal-tb ltr - Percentages are absolutized into pixels +Fail vertical-rl ltr inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-rl ltr inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Fail vertical-rl ltr inside horizontal-tb rtl - Percentages are absolutized into pixels +Fail vertical-rl ltr inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-rl ltr inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-lr ltr - Relative lengths are absolutized into pixels +Fail vertical-rl ltr inside vertical-lr ltr - Percentages are absolutized into pixels +Fail vertical-rl ltr inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-lr rtl - Relative lengths are absolutized into pixels +Fail vertical-rl ltr inside vertical-lr rtl - Percentages are absolutized into pixels +Fail vertical-rl ltr inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-rl ltr - Relative lengths are absolutized into pixels +Fail vertical-rl ltr inside vertical-rl ltr - Percentages are absolutized into pixels +Fail vertical-rl ltr inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl ltr inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-rl ltr inside vertical-rl rtl - Relative lengths are absolutized into pixels +Fail vertical-rl ltr inside vertical-rl rtl - Percentages are absolutized into pixels +Fail vertical-rl ltr inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-rl ltr inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl ltr inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside horizontal-tb ltr - Pixels resolve as-is +Pass vertical-rl rtl inside horizontal-tb ltr - Relative lengths are absolutized into pixels +Fail vertical-rl rtl inside horizontal-tb ltr - Percentages are absolutized into pixels +Fail vertical-rl rtl inside horizontal-tb ltr - calc() is absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside horizontal-tb ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside horizontal-tb ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside horizontal-tb rtl - Pixels resolve as-is +Pass vertical-rl rtl inside horizontal-tb rtl - Relative lengths are absolutized into pixels +Fail vertical-rl rtl inside horizontal-tb rtl - Percentages are absolutized into pixels +Fail vertical-rl rtl inside horizontal-tb rtl - calc() is absolutized into pixels +Pass vertical-rl rtl inside horizontal-tb rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside horizontal-tb rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside horizontal-tb rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-lr ltr - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-lr ltr - Relative lengths are absolutized into pixels +Fail vertical-rl rtl inside vertical-lr ltr - Percentages are absolutized into pixels +Fail vertical-rl rtl inside vertical-lr ltr - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-lr ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-lr ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-lr ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-lr rtl - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-lr rtl - Relative lengths are absolutized into pixels +Fail vertical-rl rtl inside vertical-lr rtl - Percentages are absolutized into pixels +Fail vertical-rl rtl inside vertical-lr rtl - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-lr rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-lr rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-lr rtl - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-rl ltr - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-rl ltr - Relative lengths are absolutized into pixels +Fail vertical-rl rtl inside vertical-rl ltr - Percentages are absolutized into pixels +Fail vertical-rl rtl inside vertical-rl ltr - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-rl ltr - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-rl ltr - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-rl ltr - If opposite sides are 'auto', they resolve to used value +Pass vertical-rl rtl inside vertical-rl rtl - Pixels resolve as-is +Pass vertical-rl rtl inside vertical-rl rtl - Relative lengths are absolutized into pixels +Fail vertical-rl rtl inside vertical-rl rtl - Percentages are absolutized into pixels +Fail vertical-rl rtl inside vertical-rl rtl - calc() is absolutized into pixels +Pass vertical-rl rtl inside vertical-rl rtl - If start side is 'auto' and end side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-rl rtl - If end side is 'auto' and start side is not, 'auto' resolves to used value +Pass vertical-rl rtl inside vertical-rl rtl - If opposite sides are 'auto', they resolve to used value \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-absolute.html b/Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-absolute.html new file mode 100644 index 00000000000..071e751918f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-absolute.html @@ -0,0 +1,21 @@ + + +CSSOM: resolved values of the inset properties for absolute positioning + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-relative.html b/Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-relative.html new file mode 100644 index 00000000000..65a5c425b93 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/cssom/getComputedStyle-insets-relative.html @@ -0,0 +1,19 @@ + + +CSSOM: resolved values of the inset properties for relative positioning + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/css/cssom/support/getComputedStyle-insets.js b/Tests/LibWeb/Text/input/wpt-import/css/cssom/support/getComputedStyle-insets.js new file mode 100644 index 00000000000..723990cafbc --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/cssom/support/getComputedStyle-insets.js @@ -0,0 +1,375 @@ +export const testEl = document.createElement("div"); +export const containerForInflow = document.createElement("div"); +export const containerForAbspos = document.createElement("div"); +export const containerForFixed = document.createElement("div"); + +testEl.id = "test"; +containerForInflow.id = "container-for-inflow"; +containerForAbspos.id = "container-for-abspos"; +containerForFixed.id = "container-for-fixed"; + +containerForInflow.appendChild(testEl); +containerForAbspos.appendChild(containerForInflow); +containerForFixed.appendChild(containerForAbspos); +document.body.appendChild(containerForFixed); + +const stylesheet = document.createElement("style"); +stylesheet.textContent = ` + #container-for-inflow { + /* Content area: 100px tall, 200px wide */ + height: 100px; + width: 200px; + padding: 1px 2px; + border-width: 2px 4px; + margin: 4px 8px; + } + #container-for-abspos { + /* Padding area: 200px tall, 400px wide */ + height: 184px; + width: 368px; + padding: 8px 16px; + border-width: 16px 32px; + margin: 32px 64px; + position: relative; + } + #container-for-fixed { + /* Padding area: 300px tall, 600px wide */ + height: 172px; + width: 344px; + padding: 64px 128px; + border-width: 128px 256px; + margin: 256px 512px; + position: absolute; + transform: scale(1); + visibility: hidden; + } + [id ^= container] { + border-style: solid; + } +`; +document.head.prepend(stylesheet); + +function runTestsWithWM(data, testWM, cbWM) { + const { + style, + containingBlockElement, + containingBlockArea, + preservesPercentages, + preservesAuto, + canStretchAutoSize, + staticPositionX, + staticPositionY, + } = data; + + let cbHeight = containingBlockElement ? containingBlockElement.clientHeight : NaN; + let cbWidth = containingBlockElement ? containingBlockElement.clientWidth : NaN; + if (containingBlockElement && containingBlockArea == "content") { + const cs = getComputedStyle(containingBlockElement); + cbHeight -= parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); + cbWidth -= parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); + } + + const staticPositionTop = cbWM.blockStart == "top" || cbWM.inlineStart == "top" + ? staticPositionY : cbHeight - staticPositionY; + const staticPositionLeft = cbWM.blockStart == "left" || cbWM.inlineStart == "left" + ? staticPositionX : cbWidth - staticPositionX; + const staticPositionBottom = cbWM.blockStart == "bottom" || cbWM.inlineStart == "bottom" + ? staticPositionY : cbHeight - staticPositionY; + const staticPositionRight = cbWM.blockStart == "right" || cbWM.inlineStart == "right" + ? staticPositionX : cbWidth - staticPositionX; + + function serialize(declarations) { + return Object.entries(declarations).map(([p, v]) => `${p}: ${v}; `).join(""); + } + + function wmName(wm) { + return Object.values(wm.style).join(" "); + } + + function checkStyle(declarations, expected, msg) { + test(function() { + testEl.style.cssText = style + "; " + serialize(Object.assign({}, declarations, testWM.style)); + if (containingBlockElement) { + containingBlockElement.style.cssText = serialize(Object.assign({}, cbWM.style)); + } + const cs = getComputedStyle(testEl); + for (let [prop, value] of Object.entries(expected)) { + assert_equals(cs[prop], value, `'${prop}'`); + } + }, `${wmName(testWM)} inside ${wmName(cbWM)} - ${msg}`); + + testEl.style.cssText = ""; + if (containingBlockElement) { + containingBlockElement.style.cssText = ""; + } + } + + checkStyle({ + top: "1px", + left: "2px", + bottom: "3px", + right: "4px", + }, { + top: "1px", + left: "2px", + bottom: "3px", + right: "4px", + }, "Pixels resolve as-is"); + + checkStyle({ + top: "1em", + left: "2em", + bottom: "3em", + right: "4em", + "font-size": "10px", + }, { + top: "10px", + left: "20px", + bottom: "30px", + right: "40px", + }, "Relative lengths are absolutized into pixels"); + + if (preservesPercentages) { + checkStyle({ + top: "10%", + left: "25%", + bottom: "50%", + right: "75%", + }, { + top: "10%", + left: "25%", + bottom: "50%", + right: "75%", + }, "Percentages resolve as-is"); + } else { + checkStyle({ + top: "10%", + left: "25%", + bottom: "50%", + right: "75%", + }, { + top: cbHeight * 10 / 100 + "px", + left: cbWidth * 25 / 100 + "px", + bottom: cbHeight * 50 / 100 + "px", + right: cbWidth * 75 / 100 + "px", + }, "Percentages are absolutized into pixels"); + + checkStyle({ + top: "calc(10% - 1px)", + left: "calc(25% - 2px)", + bottom: "calc(50% - 3px)", + right: "calc(75% - 4px)", + }, { + top: cbHeight * 10 / 100 - 1 + "px", + left: cbWidth * 25 / 100 - 2 + "px", + bottom: cbHeight * 50 / 100 - 3 + "px", + right: cbWidth * 75 / 100 - 4 + "px", + }, "calc() is absolutized into pixels"); + } + + if (canStretchAutoSize) { + // Force overconstraintment by setting size or with insets that would result in + // negative size. Then the resolved value should be the computed one according to + // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-top + + checkStyle({ + top: "1px", + left: "2px", + bottom: "3px", + right: "4px", + height: "0px", + width: "0px", + }, { + top: "1px", + left: "2px", + bottom: "3px", + right: "4px", + }, "Pixels resolve as-is when overconstrained"); + + checkStyle({ + top: "100%", + left: "100%", + bottom: "100%", + right: "100%", + }, { + top: cbHeight + "px", + left: cbWidth + "px", + bottom: cbHeight + "px", + right: cbWidth + "px", + }, "Percentages absolutize the computed value when overconstrained"); + } + + if (preservesAuto) { + checkStyle({ + top: "auto", + left: "auto", + bottom: "3px", + right: "4px", + }, { + top: "auto", + left: "auto", + bottom: "3px", + right: "4px", + }, "If start side is 'auto' and end side is not, 'auto' resolves as-is"); + + checkStyle({ + top: "1px", + left: "2px", + bottom: "auto", + right: "auto", + }, { + top: "1px", + left: "2px", + bottom: "auto", + right: "auto", + }, "If end side is 'auto' and start side is not, 'auto' resolves as-is"); + + checkStyle({ + top: "auto", + left: "auto", + bottom: "auto", + right: "auto", + }, { + top: "auto", + left: "auto", + bottom: "auto", + right: "auto", + }, "If opposite sides are 'auto', they resolve as-is"); + } else if (canStretchAutoSize) { + checkStyle({ + top: "auto", + left: "auto", + bottom: "3px", + right: "4px", + }, { + top: cbHeight - 3 + "px", + left: cbWidth - 4 + "px", + bottom: "3px", + right: "4px", + }, "If start side is 'auto' and end side is not, 'auto' resolves to used value"); + + checkStyle({ + top: "1px", + left: "2px", + bottom: "auto", + right: "auto", + }, { + top: "1px", + left: "2px", + bottom: cbHeight - 1 + "px", + right: cbWidth - 2 + "px", + }, "If end side is 'auto' and start side is not, 'auto' resolves to used value"); + + checkStyle({ + top: "auto", + left: "auto", + bottom: "auto", + right: "auto", + }, { + top: staticPositionTop + "px", + left: staticPositionLeft + "px", + bottom: staticPositionBottom + "px", + right: staticPositionRight + "px", + }, "If opposite sides are 'auto', they resolve to used value"); + } else { + checkStyle({ + top: "auto", + left: "auto", + bottom: "3px", + right: "4px", + }, { + top: "-3px", + left: "-4px", + bottom: "3px", + right: "4px", + }, "If start side is 'auto' and end side is not, 'auto' resolves to used value"); + + checkStyle({ + top: "1px", + left: "2px", + bottom: "auto", + right: "auto", + }, { + top: "1px", + left: "2px", + bottom: "-1px", + right: "-2px", + }, "If end side is 'auto' and start side is not, 'auto' resolves to used value"); + + checkStyle({ + top: "auto", + left: "auto", + bottom: "auto", + right: "auto", + }, { + top: "0px", + left: "0px", + bottom: "0px", + right: "0px", + }, "If opposite sides are 'auto', they resolve to used value"); + } +} + +const writingModes = [{ + style: { + "writing-mode": "horizontal-tb", + "direction": "ltr", + }, + blockStart: "top", + blockEnd: "bottom", + inlineStart: "left", + inlineEnd: "right", +}, { + style: { + "writing-mode": "horizontal-tb", + "direction": "rtl", + }, + blockStart: "top", + blockEnd: "bottom", + inlineStart: "right", + inlineEnd: "left", +}, { + style: { + "writing-mode": "vertical-lr", + "direction": "ltr", + }, + blockStart: "left", + blockEnd: "right", + inlineStart: "top", + inlineEnd: "bottom", +}, { + style: { + "writing-mode": "vertical-lr", + "direction": "rtl", + }, + blockStart: "left", + blockEnd: "right", + inlineStart: "bottom", + inlineEnd: "top", +}, { + style: { + "writing-mode": "vertical-rl", + "direction": "ltr", + }, + blockStart: "right", + blockEnd: "left", + inlineStart: "top", + inlineEnd: "bottom", +}, { + style: { + "writing-mode": "vertical-rl", + "direction": "rtl", + }, + blockStart: "right", + blockEnd: "left", + inlineStart: "bottom", + inlineEnd: "top", +}]; + +export function runTests(data) { + for (let testWM of writingModes) { + for (let cbWM of writingModes) { + runTestsWithWM(data, testWM, cbWM); + } + } +} From ec3c545426dac23aafdb0f98066d807030765f9a Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sat, 15 Mar 2025 15:38:09 +1300 Subject: [PATCH 017/141] LibURL+LibWeb: Ensure opaque paths always roundtrip Corresponds to: https://github.com/whatwg/url/commit/6c782003 --- Libraries/LibURL/Parser.cpp | 35 +++-- Libraries/LibWeb/DOMURL/DOMURL.cpp | 41 +----- Libraries/LibWeb/DOMURL/DOMURL.h | 5 +- Libraries/LibWeb/DOMURL/URLSearchParams.cpp | 6 +- .../expected/URL/opaque_paths_roundtrip.txt | 4 + ...-strip-trailing-spaces-from-opaque-url.txt | 8 +- ...strip_trailing_spaces_from_opaque_path.txt | 4 - .../wpt-import/url/a-element-origin.txt | 12 +- .../wpt-import/url/url-constructor.any.txt | 12 +- .../wpt-import/url/url-origin.any.txt | 12 +- .../url/url-setters-a-area.window.txt | Bin 43745 -> 44529 bytes .../wpt-import/url/url-setters.any.txt | Bin 21515 -> 21895 bytes .../url/urlsearchparams-delete.any.txt | 4 +- ..._path.html => opaque_paths_roundtrip.html} | 0 .../url/resources/setters_tests.json | 104 ++++++++++++--- .../wpt-import/url/resources/urltestdata.json | 120 ++++++++++++++++++ .../url/urlsearchparams-delete.any.js | 12 +- 17 files changed, 280 insertions(+), 99 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/URL/opaque_paths_roundtrip.txt delete mode 100644 Tests/LibWeb/Text/expected/URL/strip_trailing_spaces_from_opaque_path.txt rename Tests/LibWeb/Text/input/URL/{strip_trailing_spaces_from_opaque_path.html => opaque_paths_roundtrip.html} (100%) diff --git a/Libraries/LibURL/Parser.cpp b/Libraries/LibURL/Parser.cpp index 9c0b3638b2c..4594238410a 100644 --- a/Libraries/LibURL/Parser.cpp +++ b/Libraries/LibURL/Parser.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2021, Max Wipfli - * Copyright (c) 2023-2024, Shannon Booth + * Copyright (c) 2023-2025, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -1558,23 +1558,32 @@ Optional Parser::basic_parse(StringView raw_input, Optional bas buffer.clear(); state = State::Fragment; } - // 3. Otherwise: - else { - // 1. If c is not the EOF code point, not a URL code point, and not U+0025 (%), invalid-URL-unit validation error. - if (code_point != end_of_file && !is_url_code_point(code_point) && code_point != '%') + // 3. Otherwise, if c is U+0020 SPACE: + else if (code_point == ' ') { + // 1. If remaining starts with U+003F (?) or U+003F (#), then append "%20" to url’s path. + if (auto remaining = get_remaining(); remaining.starts_with('?') || remaining.starts_with('#')) { + buffer.append("%20"sv); + } + // 2. Otherwise, append U+0020 SPACE to url’s path. + else { + buffer.append(' '); + } + } + // 4. Otherwise, if c is not the EOF code point: + else if (code_point != end_of_file) { + // 1. If c is not a URL code point and not U+0025 (%), invalid-URL-unit validation error. + if (!is_url_code_point(code_point) && code_point != '%') report_validation_error(); - // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error. + // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error. if (code_point == '%' && !remaining_starts_with_two_ascii_hex_digits()) report_validation_error(); - // 3. If c is not the EOF code point, UTF-8 percent-encode c using the C0 control percent-encode set and append the result to url’s path. - if (code_point != end_of_file) { - append_percent_encoded_if_necessary(buffer, code_point, PercentEncodeSet::C0Control); - } else { - url->m_data->paths[0] = buffer.to_string_without_validation(); - buffer.clear(); - } + // 3. UTF-8 percent-encode c using the C0 control percent-encode set and append the result to url’s path. + append_percent_encoded_if_necessary(buffer, code_point, PercentEncodeSet::C0Control); + } else { + url->m_data->paths[0] = buffer.to_string_without_validation(); + buffer.clear(); } break; // -> query state, https://url.spec.whatwg.org/#query-state diff --git a/Libraries/LibWeb/DOMURL/DOMURL.cpp b/Libraries/LibWeb/DOMURL/DOMURL.cpp index 7945a799abb..f30b6fdc0e1 100644 --- a/Libraries/LibWeb/DOMURL/DOMURL.cpp +++ b/Libraries/LibWeb/DOMURL/DOMURL.cpp @@ -388,18 +388,10 @@ void DOMURL::set_search(String const& search) // 1. Let url be this’s URL. auto& url = m_url; - // 2. If the given value is the empty string: + // 2. If the given value is the empty string, then set url’s query to null, empty this’s query object’s list, and return. if (search.is_empty()) { - // 1. Set url’s query to null. url.set_query({}); - - // 2. Empty this’s query object’s list. m_query->m_list.clear(); - - // 3. Potentially strip trailing spaces from an opaque path with this. - strip_trailing_spaces_from_an_opaque_path(*this); - - // 4. Return. return; } @@ -438,15 +430,9 @@ String DOMURL::hash() const // https://url.spec.whatwg.org/#ref-for-dom-url-hash%E2%91%A0 void DOMURL::set_hash(String const& hash) { - // 1. If the given value is the empty string: + // 1. If the given value is the empty string, then set this’s URL’s fragment to null and return. if (hash.is_empty()) { - // 1. Set this’s URL’s fragment to null. m_url.set_fragment({}); - - // 2. Potentially strip trailing spaces from an opaque path with this. - strip_trailing_spaces_from_an_opaque_path(*this); - - // 3. Return. return; } @@ -461,29 +447,6 @@ void DOMURL::set_hash(String const& hash) (void)URL::Parser::basic_parse(input, {}, &m_url, URL::Parser::State::Fragment); } -// https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path -void strip_trailing_spaces_from_an_opaque_path(DOMURL& url) -{ - // 1. If url’s URL does not have an opaque path, then return. - // FIXME: Reimplement this step once we modernize the URL implementation to meet the spec. - if (!url.cannot_be_a_base_url()) - return; - - // 2. If url’s URL’s fragment is non-null, then return. - if (url.fragment().has_value()) - return; - - // 3. If url’s URL’s query is non-null, then return. - if (url.query().has_value()) - return; - - // 4. Remove all trailing U+0020 SPACE code points from url’s URL’s path. - // NOTE: At index 0 since the first step tells us that the URL only has one path segment. - auto opaque_path = url.path_segment_at_index(0); - auto trimmed_path = opaque_path.trim(" "sv, TrimMode::Right); - url.set_paths({ trimmed_path }); -} - // https://url.spec.whatwg.org/#concept-url-parser Optional parse(StringView input, Optional base_url, Optional encoding) { diff --git a/Libraries/LibWeb/DOMURL/DOMURL.h b/Libraries/LibWeb/DOMURL/DOMURL.h index 3dd016dc74a..66e0d958fdd 100644 --- a/Libraries/LibWeb/DOMURL/DOMURL.h +++ b/Libraries/LibWeb/DOMURL/DOMURL.h @@ -2,7 +2,7 @@ * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2021, the SerenityOS developers. * Copyright (c) 2023, networkException - * Copyright (c) 2024, Shannon Booth + * Copyright (c) 2024-2025, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -92,9 +92,6 @@ private: GC::Ref m_query; }; -// https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path -void strip_trailing_spaces_from_an_opaque_path(DOMURL& url); - // https://url.spec.whatwg.org/#concept-url-parser Optional parse(StringView input, Optional base_url = {}, Optional encoding = {}); diff --git a/Libraries/LibWeb/DOMURL/URLSearchParams.cpp b/Libraries/LibWeb/DOMURL/URLSearchParams.cpp index 2a9dc7d7475..9dac122d356 100644 --- a/Libraries/LibWeb/DOMURL/URLSearchParams.cpp +++ b/Libraries/LibWeb/DOMURL/URLSearchParams.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2021, Idan Horowitz - * Copyright (c) 2023-2024, Shannon Booth + * Copyright (c) 2023-2025, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -229,10 +229,6 @@ void URLSearchParams::update() // 4. Set query’s URL object’s URL’s query to serializedQuery. m_url->set_query({}, serialized_query); - - // 5. If serializedQuery is null, then potentially strip trailing spaces from an opaque path with query’s URL object. - if (!serialized_query.has_value()) - strip_trailing_spaces_from_an_opaque_path(*m_url); } // https://url.spec.whatwg.org/#dom-urlsearchparams-delete diff --git a/Tests/LibWeb/Text/expected/URL/opaque_paths_roundtrip.txt b/Tests/LibWeb/Text/expected/URL/opaque_paths_roundtrip.txt new file mode 100644 index 00000000000..6d17a6b22cc --- /dev/null +++ b/Tests/LibWeb/Text/expected/URL/opaque_paths_roundtrip.txt @@ -0,0 +1,4 @@ +pathname => 'foobar %20' +pathname => 'foobar %20' +pathname => 'baz%20' +pathname => 'baz%20' diff --git a/Tests/LibWeb/Text/expected/URL/search-params-strip-trailing-spaces-from-opaque-url.txt b/Tests/LibWeb/Text/expected/URL/search-params-strip-trailing-spaces-from-opaque-url.txt index 8b09e1ec3a7..1e0e2886bc9 100644 --- a/Tests/LibWeb/Text/expected/URL/search-params-strip-trailing-spaces-from-opaque-url.txt +++ b/Tests/LibWeb/Text/expected/URL/search-params-strip-trailing-spaces-from-opaque-url.txt @@ -1,6 +1,6 @@ -URL pathname is 'space ' -URL href is 'data:space ?test' +URL pathname is 'space %20' +URL href is 'data:space %20?test' true false -URL pathname is 'space' -URL href is 'data:space' +URL pathname is 'space %20' +URL href is 'data:space %20' diff --git a/Tests/LibWeb/Text/expected/URL/strip_trailing_spaces_from_opaque_path.txt b/Tests/LibWeb/Text/expected/URL/strip_trailing_spaces_from_opaque_path.txt deleted file mode 100644 index 817503998b0..00000000000 --- a/Tests/LibWeb/Text/expected/URL/strip_trailing_spaces_from_opaque_path.txt +++ /dev/null @@ -1,4 +0,0 @@ -pathname => 'foobar ' -pathname => 'foobar' -pathname => 'baz ' -pathname => 'baz' diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt b/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt index a7fb0cdf031..835c2f0c45b 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/url/a-element-origin.txt @@ -1,8 +1,8 @@ Harness status: OK -Found 386 tests +Found 394 tests -386 Pass +394 Pass Pass Loading data… Pass Parsing origin: against @@ -216,6 +216,14 @@ Pass Parsing origin: against Pass Parsing origin: against Pass Parsing origin: against Pass Parsing origin: < http://example.com/ > against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against +Pass Parsing origin: against Pass Parsing origin: against Pass Parsing origin: against Pass Parsing origin: against diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt index cd1ce01cb75..ee3f3b17030 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/url/url-constructor.any.txt @@ -1,8 +1,8 @@ Harness status: OK -Found 863 tests +Found 871 tests -863 Pass +871 Pass Pass Loading data… Pass Parsing: against @@ -283,6 +283,14 @@ Pass Parsing: against Pass Parsing: against Pass Parsing: against Pass Parsing: < http://example.com/ > without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base +Pass Parsing: without base Pass Parsing: against Pass Parsing: against Pass Parsing: against diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt index 91df4045059..bcf29a5e573 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/url/url-origin.any.txt @@ -1,8 +1,8 @@ Harness status: OK -Found 387 tests +Found 395 tests -387 Pass +395 Pass Pass Loading data… Pass Origin parsing: against @@ -216,6 +216,14 @@ Pass Origin parsing: against Pass Origin parsing: against Pass Origin parsing: against Pass Origin parsing: < http://example.com/ > without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base +Pass Origin parsing: without base Pass Origin parsing: against Pass Origin parsing: without base Pass Origin parsing: against diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-setters-a-area.window.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-setters-a-area.window.txt index 333b5c4f1d04018b955fb21363f59519d6e944e0..991f0ef23cb4b554c9c2a9dd3e4596d88f41275d 100644 GIT binary patch delta 856 zcmaEOmFeScrU}x##>NUIsl_G5TwJEch8s0p{U--VuuSr2kpPM6W#*M7=47Vm+v#QG z7ndm5DyVBs4&+wAE$KK_RtksC3Wo}iUVPGyQ)Ojw=uXSe=j7#@+&^D_l0ORpMGgyO zCi$~S;xNh12ILGpvWilolLG=pCm;5goqVB^f3iW4=;p)zOC%=wv&djEGcPeW6&^D9 z6*x|nmBV8KG=vB#ahxivh{qI^kRn1MB($&vDa>b(5F?;MQA%_2`V7&@bG4K=ug`d( zFC0>on3t}Uv z7L?onvxCFvsTJ zz$FrsH)sh=zL3W?IX8!UvqKKEfs#v6et|+sQDSCJW?s5NaY15oYOz9EQGPC5aB|LE z{mJ@sM2JzkX3kn&mwbi1{1Sx}oCfFR=jp<1oSZY)moy!l&&-|6O^jP=Rvslr*_pZ8 tlj~P?kmiWVIddH+zhBL%r$Cx-&&;)%{C>6I^Y%uj~K- diff --git a/Tests/LibWeb/Text/expected/wpt-import/url/url-setters.any.txt b/Tests/LibWeb/Text/expected/wpt-import/url/url-setters.any.txt index 63c044ee178745320bdd372fe65fb16466fb616b..8eea2b8ba1b7a5929f54317ec00c98d0b460f522 100644 GIT binary patch delta 438 zcmeBP!Pvf+17nhNl`9=*U^~nc3IV37mVwETQdUarZG{xTqgByp&+vzctDDz#Z(-I@oa zOa_axyu{p86i3P7QH10$MLfz-9f!>eFk9_xHU}DC6BiCCO3cj3%u81&E=WvHEmla( oOPTz^TWfN$cK|lI%^$pl*|14W{@|@WIUpzyhn&r3u3$z+0Ix)qqW}N^ delta 246 zcmZo)&Dgzyae_3jsii_mYH>+17nhN#<", + "expected": { + "href": "https://test.invalid/", + "host": "test.invalid", + "hostname": "test.invalid" + } } ], "hostname": [ @@ -1552,6 +1588,42 @@ "host": "example.com", "hostname": "example.com" } + }, + { + "href": "https://test.invalid/", + "new_value": "*", + "expected": { + "href": "https://*/", + "host": "*", + "hostname": "*" + } + }, + { + "href": "https://test.invalid/", + "new_value": "x@x", + "expected": { + "href": "https://test.invalid/", + "host": "test.invalid", + "hostname": "test.invalid" + } + }, + { + "href": "https://test.invalid/", + "new_value": "foo\t\r\nbar", + "expected": { + "href": "https://foobar/", + "host": "foobar", + "hostname": "foobar" + } + }, + { + "href": "https://test.invalid/", + "new_value": "><", + "expected": { + "href": "https://test.invalid/", + "host": "test.invalid", + "hostname": "test.invalid" + } } ], "port": [ @@ -2169,12 +2241,12 @@ } }, { - "comment": "Drop trailing spaces from trailing opaque paths", + "comment": "Trailing spaces and opaque paths", "href": "data:space ?query", "new_value": "", "expected": { - "href": "data:space", - "pathname": "space", + "href": "data:space%20", + "pathname": "space%20", "search": "" } }, @@ -2182,17 +2254,17 @@ "href": "sc:space ?query", "new_value": "", "expected": { - "href": "sc:space", - "pathname": "space", + "href": "sc:space%20", + "pathname": "space%20", "search": "" } }, { - "comment": "Do not drop trailing spaces from non-trailing opaque paths", + "comment": "Trailing spaces and opaque paths", "href": "data:space ?query#fragment", "new_value": "", "expected": { - "href": "data:space #fragment", + "href": "data:space %20#fragment", "search": "" } }, @@ -2200,7 +2272,7 @@ "href": "sc:space ?query#fragment", "new_value": "", "expected": { - "href": "sc:space #fragment", + "href": "sc:space %20#fragment", "search": "" } }, @@ -2357,12 +2429,12 @@ } }, { - "comment": "Drop trailing spaces from trailing opaque paths", + "comment": "Trailing spaces and opaque paths", "href": "data:space #fragment", "new_value": "", "expected": { - "href": "data:space", - "pathname": "space", + "href": "data:space %20", + "pathname": "space %20", "hash": "" } }, @@ -2370,17 +2442,17 @@ "href": "sc:space #fragment", "new_value": "", "expected": { - "href": "sc:space", - "pathname": "space", + "href": "sc:space %20", + "pathname": "space %20", "hash": "" } }, { - "comment": "Do not drop trailing spaces from non-trailing opaque paths", + "comment": "Trailing spaces and opaque paths", "href": "data:space ?query#fragment", "new_value": "", "expected": { - "href": "data:space ?query", + "href": "data:space %20?query", "hash": "" } }, @@ -2388,7 +2460,7 @@ "href": "sc:space ?query#fragment", "new_value": "", "expected": { - "href": "sc:space ?query", + "href": "sc:space %20?query", "hash": "" } }, diff --git a/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json b/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json index 214ed0852aa..d1a06f6319d 100644 --- a/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json +++ b/Tests/LibWeb/Text/input/wpt-import/url/resources/urltestdata.json @@ -3778,6 +3778,126 @@ "search": "", "hash": "" }, + { + "input": "non-special:opaque ", + "base": null, + "href": "non-special:opaque", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque", + "search": "", + "hash": "" + }, + { + "input": "non-special:opaque ?hi", + "base": null, + "href": "non-special:opaque %20?hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque %20", + "search": "?hi", + "hash": "" + }, + { + "input": "non-special:opaque #hi", + "base": null, + "href": "non-special:opaque %20#hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque %20", + "search": "", + "hash": "#hi" + }, + { + "input": "non-special:opaque x?hi", + "base": null, + "href": "non-special:opaque x?hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque x", + "search": "?hi", + "hash": "" + }, + { + "input": "non-special:opaque x#hi", + "base": null, + "href": "non-special:opaque x#hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque x", + "search": "", + "hash": "#hi" + }, + { + "input": "non-special:opaque \t\t \t#hi", + "base": null, + "href": "non-special:opaque %20#hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque %20", + "search": "", + "hash": "#hi" + }, + { + "input": "non-special:opaque \t\t #hi", + "base": null, + "href": "non-special:opaque %20#hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque %20", + "search": "", + "hash": "#hi" + }, + { + "input": "non-special:opaque\t\t \r #hi", + "base": null, + "href": "non-special:opaque %20#hi", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "opaque %20", + "search": "", + "hash": "#hi" + }, "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)", { "input": "http://www.foo。bar.com", diff --git a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js index c597142c51d..09a5dccb648 100644 --- a/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js +++ b/Tests/LibWeb/Text/input/wpt-import/url/urlsearchparams-delete.any.js @@ -50,17 +50,17 @@ test(() => { url.searchParams.delete('test'); assert_false(url.searchParams.has('test')); assert_equals(url.search, ''); - assert_equals(url.pathname, 'space'); - assert_equals(url.href, 'data:space'); -}, 'Changing the query of a URL with an opaque path can impact the path'); + assert_equals(url.pathname, 'space %20'); + assert_equals(url.href, 'data:space %20'); +}, 'Changing the query of a URL with an opaque path with trailing spaces'); test(() => { const url = new URL('data:space ?test#test'); url.searchParams.delete('test'); assert_equals(url.search, ''); - assert_equals(url.pathname, 'space '); - assert_equals(url.href, 'data:space #test'); -}, 'Changing the query of a URL with an opaque path can impact the path if the URL has no fragment'); + assert_equals(url.pathname, 'space %20'); + assert_equals(url.href, 'data:space %20#test'); +}, 'Changing the query of a URL with an opaque path with trailing spaces and a fragment'); test(() => { const params = new URLSearchParams(); From 4a7b947c5d1378f90b0679fe11b73baf6113263c Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Mon, 17 Mar 2025 22:46:58 +0100 Subject: [PATCH 018/141] LibWeb: Clamp content-based minimum size by limited max track size [GFC] Progress on https://wpt.live/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html --- .../LibWeb/Layout/GridFormattingContext.cpp | 44 +++++++++++++++---- ...minimum-size-by-limited-max-track-size.txt | 29 ++++++++++++ ...inimum-size-by-limited-max-track-size.html | 22 ++++++++++ 3 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/grid/clamp-content-based-minimum-size-by-limited-max-track-size.txt create mode 100644 Tests/LibWeb/Layout/input/grid/clamp-content-based-minimum-size-by-limited-max-track-size.html diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 94ba44d5177..30bd5fd16fd 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -2554,18 +2554,44 @@ CSSPixels GridFormattingContext::content_based_minimum_size(GridItem const& item // The content-based minimum size for a grid item in a given dimension is its specified size suggestion if it exists, // otherwise its transferred size suggestion if that exists, // else its content size suggestion, see below. - // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite. - - auto maximum_size = CSSPixels::max(); - if (auto const& css_maximum_size = get_item_maximum_size(item, dimension); css_maximum_size.is_length()) { - maximum_size = css_maximum_size.length().to_px(item.box); - } - + CSSPixels result = 0; if (auto specified_size_suggestion = this->specified_size_suggestion(item, dimension); specified_size_suggestion.has_value()) { - return min(specified_size_suggestion.value(), maximum_size); + result = specified_size_suggestion.value(); + } else { + result = content_size_suggestion(item, dimension); } - return min(content_size_suggestion(item, dimension), maximum_size); + // However, if in a given dimension the grid item spans only grid tracks that have a fixed max track sizing function, then + // its specified size suggestion and content size suggestion in that dimension (and its input from this dimension to the + // transferred size suggestion in the opposite dimension) are further clamped to less than or equal to the stretch fit into + // the grid area’s maximum size in that dimension, as represented by the sum of those grid tracks’ max track sizing functions + // plus any intervening fixed gutters. + // FIXME: Account for intervening fixed gutters. + auto const& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; + auto const& available_size = dimension == GridDimension::Column ? m_available_space->width : m_available_space->height; + auto item_track_index = item.raw_position(dimension); + auto item_track_span = item.span(dimension); + bool spans_only_tracks_with_limited_max_track_sizing_function = true; + CSSPixels sum_of_max_sizing_functions = 0; + for (size_t index = 0; index < item_track_span; index++) { + auto const& track = tracks[item_track_index + index]; + if (!track.max_track_sizing_function.is_fixed(available_size)) { + spans_only_tracks_with_limited_max_track_sizing_function = false; + break; + } + sum_of_max_sizing_functions += track.max_track_sizing_function.length_percentage().length().to_px(item.box); + } + if (spans_only_tracks_with_limited_max_track_sizing_function) { + result = min(result, sum_of_max_sizing_functions); + } + + // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite. + if (auto const& css_maximum_size = get_item_maximum_size(item, dimension); css_maximum_size.is_length()) { + auto maximum_size = css_maximum_size.length().to_px(item.box); + result = min(result, maximum_size); + } + + return result; } CSSPixels GridFormattingContext::automatic_minimum_size(GridItem const& item, GridDimension const dimension) const diff --git a/Tests/LibWeb/Layout/expected/grid/clamp-content-based-minimum-size-by-limited-max-track-size.txt b/Tests/LibWeb/Layout/expected/grid/clamp-content-based-minimum-size-by-limited-max-track-size.txt new file mode 100644 index 00000000000..d0f763794f7 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/grid/clamp-content-based-minimum-size-by-limited-max-track-size.txt @@ -0,0 +1,29 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x136 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x120 children: not-inline + Box at (13,13) content-size 50x50 [GFC] children: not-inline + BlockContainer at (13,13) content-size 0x25 [BFC] children: inline + frag 0 from TextNode start: 0, length: 10, rect: [13,13 115.625x17] baseline: 13.296875 + "XXXXXXXXXX" + TextNode <#text> + BlockContainer at (13,38) content-size 0x25 [BFC] children: not-inline + BlockContainer <(anonymous)> at (8,68) content-size 784x0 children: inline + TextNode <#text> + Box at (13,73) content-size 50x50 [GFC] children: not-inline + BlockContainer at (18,73) content-size 0x25 [BFC] children: not-inline + BlockContainer at (13,98) content-size 10x25 [BFC] children: not-inline + BlockContainer <(anonymous)> at (8,128) content-size 784x0 children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x136] + PaintableWithLines (BlockContainer) [8,8 784x120] + PaintableBox (Box
.grid) [8,8 60x60] + PaintableWithLines (BlockContainer
#a) [13,13 0x25] overflow: [13,13 115.625x17] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
#b) [13,38 0x25] + PaintableWithLines (BlockContainer(anonymous)) [8,68 784x0] + PaintableBox (Box
.grid) [8,68 60x60] + PaintableWithLines (BlockContainer
#a) [18,73 0x25] + PaintableWithLines (BlockContainer
#b) [13,98 10x25] + PaintableWithLines (BlockContainer(anonymous)) [8,128 784x0] diff --git a/Tests/LibWeb/Layout/input/grid/clamp-content-based-minimum-size-by-limited-max-track-size.html b/Tests/LibWeb/Layout/input/grid/clamp-content-based-minimum-size-by-limited-max-track-size.html new file mode 100644 index 00000000000..15224670a07 --- /dev/null +++ b/Tests/LibWeb/Layout/input/grid/clamp-content-based-minimum-size-by-limited-max-track-size.html @@ -0,0 +1,22 @@ + +
XXXXXXXXXX
+
From 6d3534ae9c1633accb1468fc4db195f72be67de7 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 14:16:01 +0000 Subject: [PATCH 019/141] Tests: Disable fDAT-inherits-cICP.html test that uses "reftest-wait" We don't support this mechanism in our test runner yet, which makes this test flaky. --- Tests/LibWeb/TestConfig.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini index 73156837f7e..c95211fe355 100644 --- a/Tests/LibWeb/TestConfig.ini +++ b/Tests/LibWeb/TestConfig.ini @@ -158,11 +158,13 @@ Text/input/wpt-import/css/css-backgrounds/animations/discrete-no-interpolation.h Text/input/ShadowDOM/css-hover-shadow-dom.html ; WPT ref tests that are flaky, probably due to not supporting class="reftest-wait" +; https://github.com/LadybirdBrowser/ladybird/issues/3984 Ref/input/wpt-import/css/css-contain/contain-layout-020.html Ref/input/wpt-import/css/css-contain/contain-paint-050.html Ref/input/wpt-import/css/css-contain/contain-paint-change-opacity.html Ref/input/wpt-import/css/css-lists/list-style-type-string-004.html Ref/input/wpt-import/css/css-transforms/individual-transform/stacking-context-001.html +Ref/input/wpt-import/png/apng/fDAT-inherits-cICP.html ; Test is flaky on CI, as navigationStart time is not set according to spec. Text/input/wpt-import/user-timing/measure_associated_with_navigation_timing.html From ea104700715760602c6e2719ef00b979ec9b7d5a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 17 Mar 2025 15:56:29 -0400 Subject: [PATCH 020/141] LibJS: Correctly print labels for some Intl objects For example, printing an Intl.Collator object would previously display: [Intl.Collator] numeric: "en" locale: "sort" usage: "variant" sensitivity: "upper" caseFirst: "default" collation: false ignorePunctuation: false We now print: [Intl.Collator] locale: "en" usage: "sort" sensitivity: "variant" caseFirst: "upper" collation: "default" ignorePunctuation: false numeric: false --- Libraries/LibJS/Print.cpp | 68 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index 24c449cc227..581436dbe5f 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -743,19 +743,19 @@ ErrorOr print_intl_plural_rules(JS::PrintContext& print_context, JS::Intl: ErrorOr print_intl_collator(JS::PrintContext& print_context, JS::Intl::Collator const& collator, HashTable& seen_objects) { TRY(print_type(print_context, "Intl.Collator"sv)); - out("\n locale: "); + TRY(js_out(print_context, "\n locale: ")); TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.locale()), seen_objects)); - out("\n usage: "); + TRY(js_out(print_context, "\n usage: ")); TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.usage_string()), seen_objects)); - out("\n sensitivity: "); + TRY(js_out(print_context, "\n sensitivity: ")); TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.sensitivity_string()), seen_objects)); - out("\n caseFirst: "); + TRY(js_out(print_context, "\n caseFirst: ")); TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.case_first_string()), seen_objects)); - out("\n collation: "); + TRY(js_out(print_context, "\n collation: ")); TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.collation()), seen_objects)); - out("\n ignorePunctuation: "); + TRY(js_out(print_context, "\n ignorePunctuation: ")); TRY(print_value(print_context, JS::Value(collator.ignore_punctuation()), seen_objects)); - out("\n numeric: "); + TRY(js_out(print_context, "\n numeric: ")); TRY(print_value(print_context, JS::Value(collator.numeric()), seen_objects)); return {}; } @@ -763,9 +763,9 @@ ErrorOr print_intl_collator(JS::PrintContext& print_context, JS::Intl::Col ErrorOr print_intl_segmenter(JS::PrintContext& print_context, JS::Intl::Segmenter const& segmenter, HashTable& seen_objects) { TRY(print_type(print_context, "Intl.Segmenter"sv)); - out("\n locale: "); + TRY(js_out(print_context, "\n locale: ")); TRY(print_value(print_context, JS::PrimitiveString::create(segmenter.vm(), segmenter.locale()), seen_objects)); - out("\n granularity: "); + TRY(js_out(print_context, "\n granularity: ")); TRY(print_value(print_context, JS::PrimitiveString::create(segmenter.vm(), segmenter.segmenter_granularity_string()), seen_objects)); return {}; } @@ -775,7 +775,7 @@ ErrorOr print_intl_segments(JS::PrintContext& print_context, JS::Intl::Seg auto segments_string = JS::Utf16String::create(segments.segments_string()); TRY(print_type(print_context, "Segments"sv)); - out("\n string: "); + TRY(js_out(print_context, "\n string: ")); TRY(print_value(print_context, JS::PrimitiveString::create(segments.vm(), move(segments_string)), seen_objects)); return {}; } @@ -783,54 +783,54 @@ ErrorOr print_intl_segments(JS::PrintContext& print_context, JS::Intl::Seg ErrorOr print_intl_duration_format(JS::PrintContext& print_context, JS::Intl::DurationFormat const& duration_format, HashTable& seen_objects) { TRY(print_type(print_context, "Intl.DurationFormat"sv)); - out("\n locale: "); + TRY(js_out(print_context, "\n locale: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.locale()), seen_objects)); - out("\n numberingSystem: "); + TRY(js_out(print_context, "\n numberingSystem: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.numbering_system()), seen_objects)); - out("\n style: "); + TRY(js_out(print_context, "\n style: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.style_string()), seen_objects)); - out("\n years: "); + TRY(js_out(print_context, "\n years: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_style_string()), seen_objects)); - out("\n yearsDisplay: "); + TRY(js_out(print_context, "\n yearsDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_display_string()), seen_objects)); - out("\n months: "); + TRY(js_out(print_context, "\n months: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_style_string()), seen_objects)); - out("\n monthsDisplay: "); + TRY(js_out(print_context, "\n monthsDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_display_string()), seen_objects)); - out("\n weeks: "); + TRY(js_out(print_context, "\n weeks: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_style_string()), seen_objects)); - out("\n weeksDisplay: "); + TRY(js_out(print_context, "\n weeksDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_display_string()), seen_objects)); - out("\n days: "); + TRY(js_out(print_context, "\n days: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_style_string()), seen_objects)); - out("\n daysDisplay: "); + TRY(js_out(print_context, "\n daysDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_display_string()), seen_objects)); - out("\n hours: "); + TRY(js_out(print_context, "\n hours: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_style_string()), seen_objects)); - out("\n hoursDisplay: "); + TRY(js_out(print_context, "\n hoursDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_display_string()), seen_objects)); - out("\n minutes: "); + TRY(js_out(print_context, "\n minutes: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_style_string()), seen_objects)); - out("\n minutesDisplay: "); + TRY(js_out(print_context, "\n minutesDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_display_string()), seen_objects)); - out("\n seconds: "); + TRY(js_out(print_context, "\n seconds: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_style_string()), seen_objects)); - out("\n secondsDisplay: "); + TRY(js_out(print_context, "\n secondsDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_display_string()), seen_objects)); - out("\n milliseconds: "); + TRY(js_out(print_context, "\n milliseconds: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_style_string()), seen_objects)); - out("\n millisecondsDisplay: "); + TRY(js_out(print_context, "\n millisecondsDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_display_string()), seen_objects)); - out("\n microseconds: "); + TRY(js_out(print_context, "\n microseconds: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_style_string()), seen_objects)); - out("\n microsecondsDisplay: "); + TRY(js_out(print_context, "\n microsecondsDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_display_string()), seen_objects)); - out("\n nanoseconds: "); + TRY(js_out(print_context, "\n nanoseconds: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_style_string()), seen_objects)); - out("\n nanosecondsDisplay: "); + TRY(js_out(print_context, "\n nanosecondsDisplay: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_display_string()), seen_objects)); if (duration_format.has_fractional_digits()) { - out("\n fractionalDigits: "); + TRY(js_out(print_context, "\n fractionalDigits: ")); TRY(print_value(print_context, JS::Value(duration_format.fractional_digits()), seen_objects)); } return {}; From 96c059bf6707167279a9f9297882b122d452c601 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 6 Dec 2024 12:15:55 -0500 Subject: [PATCH 021/141] LibJS: Use correct enum casing in some Intl constructors --- .../Runtime/Intl/NumberFormatConstructor.cpp | 26 +++++++++---------- .../Intl/RelativeTimeFormatConstructor.cpp | 8 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp index 61cd635a423..365e6842cc0 100644 --- a/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -63,13 +63,13 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb // 5. Let opt be a new Record. LocaleOptions opt {}; - // 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). + // 6. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit"). auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv)); // 7. Set opt.[[localeMatcher]] to matcher. opt.locale_matcher = matcher; - // 8. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined). + // 8. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined). auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {})); // 9. If numberingSystem is not undefined, then @@ -138,7 +138,7 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation). TRY(set_number_format_digit_options(vm, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format->notation())); - // 22. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short"). + // 22. Let compactDisplay be ? GetOption(options, "compactDisplay", STRING, « "short", "long" », "short"). auto compact_display = TRY(get_option(vm, *options, vm.names.compactDisplay, OptionType::String, { "short"sv, "long"sv }, "short"sv)); // 23. Let defaultUseGrouping be "auto". @@ -172,7 +172,7 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb // 29. Set numberFormat.[[UseGrouping]] to useGrouping. number_format->set_use_grouping(use_grouping); - // 30. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero", "negative" », "auto"). + // 30. Let signDisplay be ? GetOption(options, "signDisplay", STRING, « "auto", "never", "always", "exceptZero", "negative" », "auto"). auto sign_display = TRY(get_option(vm, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv, "negative"sv }, "auto"sv)); // 31. Set numberFormat.[[SignDisplay]] to signDisplay. @@ -224,14 +224,14 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase if (!sanctioned_rounding_increments.span().contains_slow(*rounding_increment)) return vm.throw_completion(ErrorType::IntlInvalidRoundingIncrement, *rounding_increment); - // 9. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand"). + // 9. Let roundingMode be ? GetOption(options, "roundingMode", STRING, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand"). auto rounding_mode = TRY(get_option(vm, options, vm.names.roundingMode, OptionType::String, { "ceil"sv, "floor"sv, "expand"sv, "trunc"sv, "halfCeil"sv, "halfFloor"sv, "halfExpand"sv, "halfTrunc"sv, "halfEven"sv }, "halfExpand"sv)); - // 10. Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto"). + // 10. Let roundingPriority be ? GetOption(options, "roundingPriority", STRING, « "auto", "morePrecision", "lessPrecision" », "auto"). auto rounding_priority_option = TRY(get_option(vm, options, vm.names.roundingPriority, OptionType::String, { "auto"sv, "morePrecision"sv, "lessPrecision"sv }, "auto"sv)); auto rounding_priority = rounding_priority_option.as_string().utf8_string_view(); - // 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto"). + // 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", STRING, « "auto", "stripIfInteger" », "auto"). auto trailing_zero_display = TRY(get_option(vm, options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv)); // 12. NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions. @@ -402,13 +402,13 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase // 16.1.3 SetNumberFormatUnitOptions ( intlObj, options ), https://tc39.es/ecma402/#sec-setnumberformatunitoptions ThrowCompletionOr set_number_format_unit_options(VM& vm, NumberFormat& intl_object, Object const& options) { - // 1. Let style be ? GetOption(options, "style", string, « "decimal", "percent", "currency", "unit" », "decimal"). + // 1. Let style be ? GetOption(options, "style", STRING, « "decimal", "percent", "currency", "unit" », "decimal"). auto style = TRY(get_option(vm, options, vm.names.style, OptionType::String, { "decimal"sv, "percent"sv, "currency"sv, "unit"sv }, "decimal"sv)); // 2. Set intlObj.[[Style]] to style. intl_object.set_style(style.as_string().utf8_string_view()); - // 3. Let currency be ? GetOption(options, "currency", string, empty, undefined). + // 3. Let currency be ? GetOption(options, "currency", STRING, EMPTY, undefined). auto currency = TRY(get_option(vm, options, vm.names.currency, OptionType::String, {}, Empty {})); // 4. If currency is undefined, then @@ -423,13 +423,13 @@ ThrowCompletionOr set_number_format_unit_options(VM& vm, NumberFormat& int return vm.throw_completion(ErrorType::OptionIsNotValidValue, currency, "currency"sv); } - // 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", string, « "code", "symbol", "narrowSymbol", "name" », "symbol"). + // 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", STRING, « "code", "symbol", "narrowSymbol", "name" », "symbol"). auto currency_display = TRY(get_option(vm, options, vm.names.currencyDisplay, OptionType::String, { "code"sv, "symbol"sv, "narrowSymbol"sv, "name"sv }, "symbol"sv)); - // 7. Let currencySign be ? GetOption(options, "currencySign", string, « "standard", "accounting" », "standard"). + // 7. Let currencySign be ? GetOption(options, "currencySign", STRING, « "standard", "accounting" », "standard"). auto currency_sign = TRY(get_option(vm, options, vm.names.currencySign, OptionType::String, { "standard"sv, "accounting"sv }, "standard"sv)); - // 8. Let unit be ? GetOption(options, "unit", string, empty, undefined). + // 8. Let unit be ? GetOption(options, "unit", STRING, EMPTY, undefined). auto unit = TRY(get_option(vm, options, vm.names.unit, OptionType::String, {}, Empty {})); // 9. If unit is undefined, then @@ -444,7 +444,7 @@ ThrowCompletionOr set_number_format_unit_options(VM& vm, NumberFormat& int return vm.throw_completion(ErrorType::OptionIsNotValidValue, unit, "unit"sv); } - // 11. Let unitDisplay be ? GetOption(options, "unitDisplay", string, « "short", "narrow", "long" », "short"). + // 11. Let unitDisplay be ? GetOption(options, "unitDisplay", STRING, « "short", "narrow", "long" », "short"). auto unit_display = TRY(get_option(vm, options, vm.names.unitDisplay, OptionType::String, { "short"sv, "narrow"sv, "long"sv }, "short"sv)); // 12. If style is "currency", then diff --git a/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp index 6d47a3c010a..58c09924577 100644 --- a/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp @@ -63,13 +63,13 @@ ThrowCompletionOr> RelativeTimeFormatConstructor::construct(Func // 5. Let opt be a new Record. LocaleOptions opt {}; - // 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). + // 6. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit"). auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, AK::Array { "lookup"sv, "best fit"sv }, "best fit"sv)); // 7. Set opt.[[LocaleMatcher]] to matcher. opt.locale_matcher = matcher; - // 8. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined). + // 8. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined). auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {})); // 9. If numberingSystem is not undefined, then @@ -97,13 +97,13 @@ ThrowCompletionOr> RelativeTimeFormatConstructor::construct(Func if (auto* resolved_numbering_system = result.nu.get_pointer()) relative_time_format->set_numbering_system(move(*resolved_numbering_system)); - // 16. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long"). + // 16. Let style be ? GetOption(options, "style", STRING, « "long", "short", "narrow" », "long"). auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv)); // 17. Set relativeTimeFormat.[[Style]] to style. relative_time_format->set_style(style.as_string().utf8_string_view()); - // 18. Let numeric be ? GetOption(options, "numeric", string, « "always", "auto" », "always"). + // 18. Let numeric be ? GetOption(options, "numeric", STRING, « "always", "auto" », "always"). auto numeric = TRY(get_option(vm, *options, vm.names.numeric, OptionType::String, { "always"sv, "auto"sv }, "always"sv)); // 19. Set relativeTimeFormat.[[Numeric]] to numeric. From 37b8ba96f1ee344bcdf0292e3637f3af261a66ff Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 6 Dec 2024 12:16:33 -0500 Subject: [PATCH 022/141] LibJS: Use currency digits for NumberFormat only for standard notation This is a normative change in the ECMA-402 spec. See: https://github.com/tc39/ecma402/commit/9140da2 --- .../Runtime/Intl/NumberFormatConstructor.cpp | 24 +++++++++---------- .../NumberFormat.prototype.resolvedOptions.js | 20 ++++++++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp index 365e6842cc0..041a2c1d0d2 100644 --- a/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -100,11 +100,17 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb // 16. Let style be numberFormat.[[Style]]. auto style = number_format->style(); + // 17. Let notation be ? GetOption(options, "notation", STRING, « "standard", "scientific", "engineering", "compact" », "standard"). + auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv)); + + // 18. Set numberFormat.[[Notation]] to notation. + number_format->set_notation(notation.as_string().utf8_string_view()); + int default_min_fraction_digits = 0; int default_max_fraction_digits = 0; - // 17. If style is "currency", then - if (style == Unicode::NumberFormatStyle::Currency) { + // 19. If style is "currency" and notation is "standard", then + if (style == Unicode::NumberFormatStyle::Currency && number_format->notation() == Unicode::Notation::Standard) { // a. Let currency be numberFormat.[[Currency]]. auto const& currency = number_format->currency(); @@ -117,7 +123,7 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb // d. Let mxfdDefault be cDigits. default_max_fraction_digits = digits; } - // 18. Else, + // 20. Else, else { // a. Let mnfdDefault be 0. default_min_fraction_digits = 0; @@ -129,12 +135,6 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb default_max_fraction_digits = style == Unicode::NumberFormatStyle::Percent ? 0 : 3; } - // 19. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard"). - auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv)); - - // 20. Set numberFormat.[[Notation]] to notation. - number_format->set_notation(notation.as_string().utf8_string_view()); - // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation). TRY(set_number_format_digit_options(vm, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format->notation())); @@ -266,8 +266,8 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase // a. Set needSd to hasSd. need_significant_digits = has_significant_digits; - // b. If hasSd is true, or hasFd is false and notation is "compact", then - if (has_significant_digits || (!has_fraction_digits && notation == Unicode::Notation::Compact)) { + // b. If needSd is true, or hasFd is false and notation is "compact", then + if (need_significant_digits || (!has_fraction_digits && notation == Unicode::Notation::Compact)) { // i. Set needFd to false. need_fraction_digits = false; } @@ -390,7 +390,7 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase if (intl_object.rounding_type() != Unicode::RoundingType::FractionDigits) return vm.throw_completion(ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, intl_object.rounding_type_string()); - // b. If intlObj.[[MaximumFractionDigits]] is not equal to intlObj.[[MinimumFractionDigits]], throw a RangeError exception. + // b. If intlObj.[[MaximumFractionDigits]] is not intlObj.[[MinimumFractionDigits]], throw a RangeError exception. if (intl_object.max_fraction_digits() != intl_object.min_fraction_digits()) return vm.throw_completion(ErrorType::IntlInvalidRoundingIncrementForFractionDigits, *rounding_increment); } diff --git a/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js b/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js index 319fbaef98b..2a2d2079fec 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js +++ b/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js @@ -95,6 +95,26 @@ describe("correct behavior", () => { expect(en2.resolvedOptions().maximumFractionDigits).toBe(2); }); + test("currency digits are not used for non-standard notation", () => { + const en1 = new Intl.NumberFormat("en", { + style: "currency", + currency: "USD", + notation: "engineering", + }); + expect(en1.resolvedOptions().style).toBe("currency"); + expect(en1.resolvedOptions().minimumFractionDigits).toBe(0); + expect(en1.resolvedOptions().maximumFractionDigits).toBe(3); + + const en2 = new Intl.NumberFormat("en", { + style: "currency", + currency: "USD", + notation: "compact", + }); + expect(en2.resolvedOptions().style).toBe("currency"); + expect(en2.resolvedOptions().minimumFractionDigits).toBe(0); + expect(en2.resolvedOptions().maximumFractionDigits).toBe(0); + }); + test("other style options default to min fraction digits of 0 and max fraction digits of 0 or 3", () => { const en1 = new Intl.NumberFormat("en", { style: "decimal" }); expect(en1.resolvedOptions().style).toBe("decimal"); From 00d00b84d366a982ce2dc6085135eb26953ca258 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 17 Mar 2025 16:24:09 -0400 Subject: [PATCH 023/141] LibJS: Ensure relevant extension keys are included in ICU locale data This is a normative change in the ECMA-402 spec. See: https://github.com/tc39/ecma402/commit/7508197 In our implementation, we don't have the affected AOs directly, as we delegate to ICU. So instead, we must ensure we provide ICU a locale with the relevant extension keys present. --- .../LibJS/Runtime/Intl/AbstractOperations.cpp | 15 ++++++ .../LibJS/Runtime/Intl/AbstractOperations.h | 1 + .../LibJS/Runtime/Intl/DateTimeFormat.cpp | 12 ++--- Libraries/LibJS/Runtime/Intl/DateTimeFormat.h | 4 ++ .../Intl/DateTimeFormatConstructor.cpp | 5 +- .../Intl/DurationFormatConstructor.cpp | 2 +- .../Runtime/Intl/NumberFormatConstructor.cpp | 3 +- .../Runtime/Intl/PluralRulesConstructor.cpp | 3 +- .../Intl/RelativeTimeFormatConstructor.cpp | 2 +- .../DateTimeFormat.prototype.format.js | 8 ++-- .../DateTimeFormat.prototype.formatRange.js | 8 ++-- ...TimeFormat.prototype.formatRangeToParts.js | 46 +++++++++---------- .../DateTimeFormat.prototype.formatToParts.js | 24 +++++----- .../RelativeTimeFormat.prototype.format.js | 5 ++ Libraries/LibUnicode/DurationFormat.cpp | 2 +- Libraries/LibUnicode/NumberFormat.cpp | 8 ---- Libraries/LibUnicode/NumberFormat.h | 1 - 17 files changed, 82 insertions(+), 67 deletions(-) diff --git a/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp index 166f7a84c94..486209ee3f2 100644 --- a/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp @@ -500,6 +500,8 @@ ResolvedLocale resolve_locale(ReadonlySpan requested_locales, LocaleOpti // 12. Let supportedKeywords be a new empty List. Vector supported_keywords; + Vector icu_keywords; + // 13. For each element key of relevantExtensionKeys, do for (auto const& key : relevant_extension_keys) { // a. Let keyLocaleData be foundLocaleData.[[]]. @@ -574,10 +576,23 @@ ResolvedLocale resolve_locale(ReadonlySpan requested_locales, LocaleOpti if (supported_keyword.has_value()) supported_keywords.append(supported_keyword.release_value()); + if (auto* value_string = value.get_pointer()) + icu_keywords.empend(MUST(String::from_utf8(key)), *value_string); + // m. Set result.[[]] to value. find_key_in_value(result, key) = move(value); } + // AD-HOC: For ICU, we need to form a locale with all relevant extension keys present. + if (icu_keywords.is_empty()) { + result.icu_locale = found_locale; + } else { + auto locale_id = Unicode::parse_unicode_locale_id(found_locale); + VERIFY(locale_id.has_value()); + + result.icu_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), {}, move(icu_keywords)); + } + // 14. If supportedKeywords is not empty, then if (!supported_keywords.is_empty()) { auto locale_id = Unicode::parse_unicode_locale_id(found_locale); diff --git a/Libraries/LibJS/Runtime/Intl/AbstractOperations.h b/Libraries/LibJS/Runtime/Intl/AbstractOperations.h index 14adeabceb7..1d0d3f9f644 100644 --- a/Libraries/LibJS/Runtime/Intl/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/Intl/AbstractOperations.h @@ -38,6 +38,7 @@ struct MatchedLocale { struct ResolvedLocale { String locale; + String icu_locale; LocaleKey ca; // [[Calendar]] LocaleKey co; // [[Collation]] LocaleKey hc; // [[HourCycle]] diff --git a/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp b/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp index 36e49b69906..2b0306b7036 100644 --- a/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp +++ b/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp @@ -47,32 +47,32 @@ static Optional get_or_create_formatter(StringVi Optional DateTimeFormat::temporal_plain_date_formatter() { - return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format); + return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format); } Optional DateTimeFormat::temporal_plain_year_month_formatter() { - return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format); + return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format); } Optional DateTimeFormat::temporal_plain_month_day_formatter() { - return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format); + return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format); } Optional DateTimeFormat::temporal_plain_time_formatter() { - return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format); + return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format); } Optional DateTimeFormat::temporal_plain_date_time_formatter() { - return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format); + return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format); } Optional DateTimeFormat::temporal_instant_formatter() { - return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format); + return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format); } // 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern diff --git a/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h b/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h index b758bdd09d6..2179f004cb1 100644 --- a/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h +++ b/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h @@ -38,6 +38,9 @@ public: String const& locale() const { return m_locale; } void set_locale(String locale) { m_locale = move(locale); } + String const& icu_locale() const { return m_icu_locale; } + void set_icu_locale(String icu_locale) { m_icu_locale = move(icu_locale); } + String const& calendar() const { return m_calendar; } void set_calendar(String calendar) { m_calendar = move(calendar); } @@ -107,6 +110,7 @@ private: GC::Ptr m_bound_format; // [[BoundFormat]] // Non-standard. Stores the ICU date-time formatters for the Intl object's formatting options. + String m_icu_locale; OwnPtr m_formatter; OwnPtr m_temporal_plain_date_formatter; OwnPtr m_temporal_plain_year_month_formatter; diff --git a/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp index c8a33df7797..c68d1cdc887 100644 --- a/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp @@ -149,6 +149,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct // 18. Set dateTimeFormat.[[Locale]] to r.[[Locale]]. date_time_format->set_locale(move(result.locale)); + date_time_format->set_icu_locale(move(result.icu_locale)); // 19. Let resolvedCalendar be r.[[ca]]. // 20. Set dateTimeFormat.[[Calendar]] to resolvedCalendar. @@ -355,7 +356,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct // d. Let styles be resolvedLocaleData.[[styles]].[[]]. // e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles). formatter = Unicode::DateTimeFormat::create_for_date_and_time_style( - date_time_format->locale(), + date_time_format->icu_locale(), time_zone, format_options.hour_cycle, format_options.hour12, @@ -443,7 +444,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct } formatter = Unicode::DateTimeFormat::create_for_pattern_options( - date_time_format->locale(), + date_time_format->icu_locale(), time_zone, best_format); } diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp index 7fd57e81823..d5513cb0b45 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp @@ -89,7 +89,7 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function // 11. Let resolvedLocaleData be r.[[LocaleData]]. // 12. Let digitalFormat be resolvedLocaleData.[[DigitalFormat]]. - auto digital_format = Unicode::digital_format(duration_format->locale()); + auto digital_format = Unicode::digital_format(result.icu_locale); // 13. Set durationFormat.[[HourMinuteSeparator]] to digitalFormat.[[HourMinuteSeparator]]. duration_format->set_hour_minute_separator(move(digital_format.hours_minutes_separator)); diff --git a/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp index 041a2c1d0d2..15967bb8e6a 100644 --- a/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -184,8 +184,7 @@ ThrowCompletionOr> NumberFormatConstructor::construct(FunctionOb // Non-standard, create an ICU number formatter for this Intl object. auto formatter = Unicode::NumberFormat::create( - number_format->locale(), - number_format->numbering_system(), + result.icu_locale, number_format->display_options(), number_format->rounding_options()); number_format->set_formatter(move(formatter)); diff --git a/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp b/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp index df0a4aaf63a..f2ca85d3f2a 100644 --- a/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp @@ -87,8 +87,7 @@ ThrowCompletionOr> PluralRulesConstructor::construct(FunctionObj // Non-standard, create an ICU number formatter for this Intl object. auto formatter = Unicode::NumberFormat::create( - plural_rules->locale(), - {}, + result.icu_locale, {}, plural_rules->rounding_options()); diff --git a/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp index 58c09924577..75da8e74b98 100644 --- a/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp @@ -112,7 +112,7 @@ ThrowCompletionOr> RelativeTimeFormatConstructor::construct(Func // 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%Intl.NumberFormat%, « locale »). // 21. Let relativeTimeFormat.[[PluralRules]] be ! Construct(%Intl.PluralRules%, « locale »). auto formatter = Unicode::RelativeTimeFormat::create( - relative_time_format->locale(), + result.icu_locale, relative_time_format->style()); relative_time_format->set_formatter(move(formatter)); diff --git a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js index 0300fb3090a..f34f8616f90 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js +++ b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js @@ -652,17 +652,17 @@ describe("Temporal objects", () => { test("Temporal.PlainDate", () => { const plainDate = new Temporal.PlainDate(1989, 1, 23); - expect(formatter.format(plainDate)).toBe("1/23/1989"); + expect(formatter.format(plainDate)).toBe("1989-01-23"); }); test("Temporal.PlainYearMonth", () => { const plainYearMonth = new Temporal.PlainYearMonth(1989, 1); - expect(formatter.format(plainYearMonth)).toBe("1/1989"); + expect(formatter.format(plainYearMonth)).toBe("1989-01"); }); test("Temporal.PlainMonthDay", () => { const plainMonthDay = new Temporal.PlainMonthDay(1, 23); - expect(formatter.format(plainMonthDay)).toBe("1/23"); + expect(formatter.format(plainMonthDay)).toBe("01-23"); }); test("Temporal.PlainTime", () => { @@ -672,6 +672,6 @@ describe("Temporal objects", () => { test("Temporal.Instant", () => { const instant = new Temporal.Instant(1732740069000000000n); - expect(formatter.format(instant)).toBe("11/27/2024, 8:41:09 PM"); + expect(formatter.format(instant)).toBe("2024-11-27, 8:41:09 PM"); }); }); diff --git a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRange.js b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRange.js index 1ea6bf59476..3b7fe4dddf1 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRange.js +++ b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRange.js @@ -322,19 +322,19 @@ describe("Temporal objects", () => { test("Temporal.PlainDate", () => { const plainDate1 = new Temporal.PlainDate(1989, 1, 23); const plainDate2 = new Temporal.PlainDate(2024, 11, 27); - expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1/23/1989 – 11/27/2024"); + expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1989-01-23 – 2024-11-27"); }); test("Temporal.PlainYearMonth", () => { const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1); const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11); - expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1/1989 – 11/2024"); + expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1989-01 – 2024-11"); }); test("Temporal.PlainMonthDay", () => { const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23); const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27); - expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("1/23 – 11/27"); + expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("01-23 – 11-27"); }); test("Temporal.PlainTime", () => { @@ -347,7 +347,7 @@ describe("Temporal objects", () => { const instant1 = new Temporal.Instant(601546251000000000n); const instant2 = new Temporal.Instant(1732740069000000000n); expect(formatter.formatRange(instant1, instant2)).toBe( - "1/23/1989, 8:10:51 AM – 11/27/2024, 8:41:09 PM" + "1989-01-23, 8:10:51 AM – 2024-11-27, 8:41:09 PM" ); }); }); diff --git a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRangeToParts.js b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRangeToParts.js index 57548cda756..d66faef2c29 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRangeToParts.js +++ b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatRangeToParts.js @@ -736,17 +736,17 @@ describe("Temporal objects", () => { const plainDate1 = new Temporal.PlainDate(1989, 1, 23); const plainDate2 = new Temporal.PlainDate(2024, 11, 27); expect(formatter.formatRangeToParts(plainDate1, plainDate2)).toEqual([ - { type: "month", value: "1", source: "startRange" }, - { type: "literal", value: "/", source: "startRange" }, - { type: "day", value: "23", source: "startRange" }, - { type: "literal", value: "/", source: "startRange" }, { type: "year", value: "1989", source: "startRange" }, + { type: "literal", value: "-", source: "startRange" }, + { type: "month", value: "01", source: "startRange" }, + { type: "literal", value: "-", source: "startRange" }, + { type: "day", value: "23", source: "startRange" }, { type: "literal", value: " – ", source: "shared" }, - { type: "month", value: "11", source: "endRange" }, - { type: "literal", value: "/", source: "endRange" }, - { type: "day", value: "27", source: "endRange" }, - { type: "literal", value: "/", source: "endRange" }, { type: "year", value: "2024", source: "endRange" }, + { type: "literal", value: "-", source: "endRange" }, + { type: "month", value: "11", source: "endRange" }, + { type: "literal", value: "-", source: "endRange" }, + { type: "day", value: "27", source: "endRange" }, ]); }); @@ -754,13 +754,13 @@ describe("Temporal objects", () => { const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1); const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11); expect(formatter.formatRangeToParts(plainYearMonth1, plainYearMonth2)).toEqual([ - { type: "month", value: "1", source: "startRange" }, - { type: "literal", value: "/", source: "startRange" }, { type: "year", value: "1989", source: "startRange" }, + { type: "literal", value: "-", source: "startRange" }, + { type: "month", value: "01", source: "startRange" }, { type: "literal", value: " – ", source: "shared" }, - { type: "month", value: "11", source: "endRange" }, - { type: "literal", value: "/", source: "endRange" }, { type: "year", value: "2024", source: "endRange" }, + { type: "literal", value: "-", source: "endRange" }, + { type: "month", value: "11", source: "endRange" }, ]); }); @@ -768,12 +768,12 @@ describe("Temporal objects", () => { const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23); const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27); expect(formatter.formatRangeToParts(plainMonthDay1, plainMonthDay2)).toEqual([ - { type: "month", value: "1", source: "startRange" }, - { type: "literal", value: "/", source: "startRange" }, + { type: "month", value: "01", source: "startRange" }, + { type: "literal", value: "-", source: "startRange" }, { type: "day", value: "23", source: "startRange" }, { type: "literal", value: " – ", source: "shared" }, { type: "month", value: "11", source: "endRange" }, - { type: "literal", value: "/", source: "endRange" }, + { type: "literal", value: "-", source: "endRange" }, { type: "day", value: "27", source: "endRange" }, ]); }); @@ -804,11 +804,11 @@ describe("Temporal objects", () => { const instant1 = new Temporal.Instant(601546251000000000n); const instant2 = new Temporal.Instant(1732740069000000000n); expect(formatter.formatRangeToParts(instant1, instant2)).toEqual([ - { type: "month", value: "1", source: "startRange" }, - { type: "literal", value: "/", source: "startRange" }, - { type: "day", value: "23", source: "startRange" }, - { type: "literal", value: "/", source: "startRange" }, { type: "year", value: "1989", source: "startRange" }, + { type: "literal", value: "-", source: "startRange" }, + { type: "month", value: "01", source: "startRange" }, + { type: "literal", value: "-", source: "startRange" }, + { type: "day", value: "23", source: "startRange" }, { type: "literal", value: ", ", source: "startRange" }, { type: "hour", value: "8", source: "startRange" }, { type: "literal", value: ":", source: "startRange" }, @@ -818,11 +818,11 @@ describe("Temporal objects", () => { { type: "literal", value: " ", source: "startRange" }, { type: "dayPeriod", value: "AM", source: "startRange" }, { type: "literal", value: " – ", source: "shared" }, - { type: "month", value: "11", source: "endRange" }, - { type: "literal", value: "/", source: "endRange" }, - { type: "day", value: "27", source: "endRange" }, - { type: "literal", value: "/", source: "endRange" }, { type: "year", value: "2024", source: "endRange" }, + { type: "literal", value: "-", source: "endRange" }, + { type: "month", value: "11", source: "endRange" }, + { type: "literal", value: "-", source: "endRange" }, + { type: "day", value: "27", source: "endRange" }, { type: "literal", value: ", ", source: "endRange" }, { type: "hour", value: "8", source: "endRange" }, { type: "literal", value: ":", source: "endRange" }, diff --git a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatToParts.js b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatToParts.js index 9ca0b871c91..ed7137af970 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatToParts.js +++ b/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.formatToParts.js @@ -350,28 +350,28 @@ describe("Temporal objects", () => { test("Temporal.PlainDate", () => { const plainDate = new Temporal.PlainDate(1989, 1, 23); expect(formatter.formatToParts(plainDate)).toEqual([ - { type: "month", value: "1" }, - { type: "literal", value: "/" }, - { type: "day", value: "23" }, - { type: "literal", value: "/" }, { type: "year", value: "1989" }, + { type: "literal", value: "-" }, + { type: "month", value: "01" }, + { type: "literal", value: "-" }, + { type: "day", value: "23" }, ]); }); test("Temporal.PlainYearMonth", () => { const plainYearMonth = new Temporal.PlainYearMonth(1989, 1); expect(formatter.formatToParts(plainYearMonth)).toEqual([ - { type: "month", value: "1" }, - { type: "literal", value: "/" }, { type: "year", value: "1989" }, + { type: "literal", value: "-" }, + { type: "month", value: "01" }, ]); }); test("Temporal.PlainMonthDay", () => { const plainMonthDay = new Temporal.PlainMonthDay(1, 23); expect(formatter.formatToParts(plainMonthDay)).toEqual([ - { type: "month", value: "1" }, - { type: "literal", value: "/" }, + { type: "month", value: "01" }, + { type: "literal", value: "-" }, { type: "day", value: "23" }, ]); }); @@ -392,11 +392,11 @@ describe("Temporal objects", () => { test("Temporal.Instant", () => { const instant = new Temporal.Instant(1732740069000000000n); expect(formatter.formatToParts(instant)).toEqual([ - { type: "month", value: "11" }, - { type: "literal", value: "/" }, - { type: "day", value: "27" }, - { type: "literal", value: "/" }, { type: "year", value: "2024" }, + { type: "literal", value: "-" }, + { type: "month", value: "11" }, + { type: "literal", value: "-" }, + { type: "day", value: "27" }, { type: "literal", value: ", " }, { type: "hour", value: "8" }, { type: "literal", value: ":" }, diff --git a/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.format.js b/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.format.js index def30e3c5b2..95b5fa1568a 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.format.js +++ b/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.format.js @@ -129,6 +129,11 @@ describe("second", () => { runTest("second", "narrow", "auto", en, ar, pl); }); + + test("numberingSystem set via locale options", () => { + const formatter = new Intl.RelativeTimeFormat("en", { numberingSystem: "arab" }); + expect(formatter.format(1, "second")).toBe("in ١ second"); + }); }); describe("minute", () => { diff --git a/Libraries/LibUnicode/DurationFormat.cpp b/Libraries/LibUnicode/DurationFormat.cpp index cadca63d307..7c637ab4eda 100644 --- a/Libraries/LibUnicode/DurationFormat.cpp +++ b/Libraries/LibUnicode/DurationFormat.cpp @@ -38,7 +38,7 @@ DigitalFormat digital_format(StringView locale) rounding_options.min_significant_digits = 1; rounding_options.max_significant_digits = 2; - auto number_formatter = NumberFormat::create(locale, "latn"sv, {}, rounding_options); + auto number_formatter = NumberFormat::create(locale, {}, rounding_options); auto icu_locale = adopt_own(*locale_data->locale().clone()); icu_locale->setUnicodeKeywordValue("nu", "latn", status); diff --git a/Libraries/LibUnicode/NumberFormat.cpp b/Libraries/LibUnicode/NumberFormat.cpp index a44d6aba57d..6e713bd029b 100644 --- a/Libraries/LibUnicode/NumberFormat.cpp +++ b/Libraries/LibUnicode/NumberFormat.cpp @@ -866,12 +866,9 @@ private: NonnullOwnPtr NumberFormat::create( StringView locale, - StringView numbering_system, DisplayOptions const& display_options, RoundingOptions const& rounding_options) { - UErrorCode status = U_ZERO_ERROR; - auto locale_data = LocaleData::for_locale(locale); VERIFY(locale_data.has_value()); @@ -879,11 +876,6 @@ NonnullOwnPtr NumberFormat::create( apply_display_options(formatter, display_options); apply_rounding_options(formatter, rounding_options); - if (!numbering_system.is_empty()) { - if (auto* symbols = icu::NumberingSystem::createInstanceByName(ByteString(numbering_system).characters(), status); symbols && icu_success(status)) - formatter = formatter.adoptSymbols(symbols); - } - bool is_unit = display_options.style == NumberFormatStyle::Unit; return adopt_own(*new NumberFormatImpl(locale_data->locale(), move(formatter), is_unit)); } diff --git a/Libraries/LibUnicode/NumberFormat.h b/Libraries/LibUnicode/NumberFormat.h index e9bc4612637..929160f4517 100644 --- a/Libraries/LibUnicode/NumberFormat.h +++ b/Libraries/LibUnicode/NumberFormat.h @@ -142,7 +142,6 @@ class NumberFormat { public: static NonnullOwnPtr create( StringView locale, - StringView numbering_system, DisplayOptions const&, RoundingOptions const&); From 780de1395bcb5681f392f5b27a840800966a8c14 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 18 Mar 2025 08:38:05 -0400 Subject: [PATCH 024/141] LibJS: Merge Intl.DurationFormat style and display fields into a struct This is an editorial change in the ECMA-402 spec. See: https://github.com/tc39/ecma402/commit/d56d624 --- Libraries/LibJS/Print.cpp | 65 +++---- .../LibJS/Runtime/Intl/DurationFormat.cpp | 136 +++++++------- Libraries/LibJS/Runtime/Intl/DurationFormat.h | 174 ++++++------------ .../Intl/DurationFormatConstructor.cpp | 30 ++- .../Runtime/Intl/DurationFormatPrototype.cpp | 80 ++++---- .../Runtime/Intl/ListFormatPrototype.cpp | 2 +- .../Runtime/Intl/NumberFormatPrototype.cpp | 2 +- .../Runtime/Intl/PluralRulesPrototype.cpp | 2 +- .../Intl/RelativeTimeFormatPrototype.cpp | 2 +- .../LibJS/Runtime/Intl/SegmenterPrototype.cpp | 2 +- 10 files changed, 213 insertions(+), 282 deletions(-) diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index 581436dbe5f..dd6c09b556f 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -782,6 +782,19 @@ ErrorOr print_intl_segments(JS::PrintContext& print_context, JS::Intl::Seg ErrorOr print_intl_duration_format(JS::PrintContext& print_context, JS::Intl::DurationFormat const& duration_format, HashTable& seen_objects) { + auto print_style_and_display = [&](StringView style_name, StringView display_name, JS::Intl::DurationFormat::DurationUnitOptions options) -> ErrorOr { + auto style = JS::Intl::DurationFormat::value_style_to_string(options.style); + auto display = JS::Intl::DurationFormat::display_to_string(options.display); + + TRY(js_out(print_context, "\n {}: ", style_name)); + TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), style), seen_objects)); + + TRY(js_out(print_context, "\n {}: ", display_name)); + TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), display), seen_objects)); + + return {}; + }; + TRY(print_type(print_context, "Intl.DurationFormat"sv)); TRY(js_out(print_context, "\n locale: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.locale()), seen_objects)); @@ -789,46 +802,18 @@ ErrorOr print_intl_duration_format(JS::PrintContext& print_context, JS::In TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.numbering_system()), seen_objects)); TRY(js_out(print_context, "\n style: ")); TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.style_string()), seen_objects)); - TRY(js_out(print_context, "\n years: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_style_string()), seen_objects)); - TRY(js_out(print_context, "\n yearsDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_display_string()), seen_objects)); - TRY(js_out(print_context, "\n months: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_style_string()), seen_objects)); - TRY(js_out(print_context, "\n monthsDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_display_string()), seen_objects)); - TRY(js_out(print_context, "\n weeks: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_style_string()), seen_objects)); - TRY(js_out(print_context, "\n weeksDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_display_string()), seen_objects)); - TRY(js_out(print_context, "\n days: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_style_string()), seen_objects)); - TRY(js_out(print_context, "\n daysDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_display_string()), seen_objects)); - TRY(js_out(print_context, "\n hours: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_style_string()), seen_objects)); - TRY(js_out(print_context, "\n hoursDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_display_string()), seen_objects)); - TRY(js_out(print_context, "\n minutes: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_style_string()), seen_objects)); - TRY(js_out(print_context, "\n minutesDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_display_string()), seen_objects)); - TRY(js_out(print_context, "\n seconds: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_style_string()), seen_objects)); - TRY(js_out(print_context, "\n secondsDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_display_string()), seen_objects)); - TRY(js_out(print_context, "\n milliseconds: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_style_string()), seen_objects)); - TRY(js_out(print_context, "\n millisecondsDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_display_string()), seen_objects)); - TRY(js_out(print_context, "\n microseconds: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_style_string()), seen_objects)); - TRY(js_out(print_context, "\n microsecondsDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_display_string()), seen_objects)); - TRY(js_out(print_context, "\n nanoseconds: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_style_string()), seen_objects)); - TRY(js_out(print_context, "\n nanosecondsDisplay: ")); - TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_display_string()), seen_objects)); + + TRY(print_style_and_display("years"sv, "yearsDisplay"sv, duration_format.years_options())); + TRY(print_style_and_display("months"sv, "monthsDisplay"sv, duration_format.months_options())); + TRY(print_style_and_display("weeks"sv, "weeksDisplay"sv, duration_format.weeks_options())); + TRY(print_style_and_display("days"sv, "daysDisplay"sv, duration_format.days_options())); + TRY(print_style_and_display("hours"sv, "hoursDisplay"sv, duration_format.hours_options())); + TRY(print_style_and_display("minutes"sv, "minutesDisplay"sv, duration_format.minutes_options())); + TRY(print_style_and_display("seconds"sv, "secondsDisplay"sv, duration_format.seconds_options())); + TRY(print_style_and_display("milliseconds"sv, "millisecondsDisplay"sv, duration_format.milliseconds_options())); + TRY(print_style_and_display("microseconds"sv, "microsecondsDisplay"sv, duration_format.microseconds_options())); + TRY(print_style_and_display("nanoseconds"sv, "nanosecondsDisplay"sv, duration_format.nanoseconds_options())); + if (duration_format.has_fractional_digits()) { TRY(js_out(print_context, "\n fractionalDigits: ")); TRY(print_value(print_context, JS::Value(duration_format.fractional_digits()), seen_objects)); diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp index e7c363089fc..788c43cf631 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp @@ -208,7 +208,7 @@ static ThrowCompletionOr validate_duration_unit_style(VM& vm, PropertyKey } // 13.5.6 GetDurationUnitOptions ( unit, options, baseStyle, stylesList, digitalBase, prevStyle, twoDigitHours ), https://tc39.es/ecma402/#sec-getdurationunitoptions -ThrowCompletionOr get_duration_unit_options(VM& vm, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan styles_list, DurationFormat::ValueStyle digital_base, Optional previous_style, bool two_digit_hours) +ThrowCompletionOr get_duration_unit_options(VM& vm, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan styles_list, DurationFormat::ValueStyle digital_base, Optional previous_style, bool two_digit_hours) { auto const& unit_property_key = unit_to_property_key(vm, unit); @@ -251,8 +251,8 @@ ThrowCompletionOr get_duration_unit_options(VM& vm, Duratio style = DurationFormat::value_style_from_string(style_value.as_string().utf8_string_view()); } - // 4. If style is "numeric" and unit is one of "milliseconds", "microseconds", or "nanoseconds", then - if (style == DurationFormat::ValueStyle::Numeric && first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds)) { + // 4. If style is "numeric" and IsFractionalSecondUnitName(unit) is true, then + if (style == DurationFormat::ValueStyle::Numeric && is_fractional_second_unit_name(unit)) { // a. Set style to "fractional". style = DurationFormat::ValueStyle::Fractional; @@ -280,8 +280,8 @@ ThrowCompletionOr get_duration_unit_options(VM& vm, Duratio style = DurationFormat::ValueStyle::TwoDigit; } - // 10. Return the Record { [[Style]]: style, [[Display]]: display }. - return DurationUnitOptions { .style = style, .display = display }; + // 10. Return the Duration Unit Options Record { [[Style]]: style, [[Display]]: display }. + return DurationFormat::DurationUnitOptions { .style = style, .display = display }; } // 13.5.7 ComputeFractionalDigits ( durationFormat, duration ), https://tc39.es/ecma402/#sec-computefractionaldigits @@ -294,25 +294,25 @@ Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_for // 2. Let exponent be 3. double exponent = 3; - // 3. For each row of Table 23, except the header row, in table order, do + // 3. For each row of Table 24, except the header row, in table order, do for (auto const& duration_instances_component : duration_instances_components) { - // a. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row. - auto style = (duration_format.*duration_instances_component.get_style_slot)(); - // b. If style is "fractional", then - if (style == DurationFormat::ValueStyle::Fractional) { - // i. Assert: The Unit value of the current row is "milliseconds", "microseconds", or "nanoseconds". - VERIFY(first_is_one_of(duration_instances_component.unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds)); + // a. Let unitOptions be the value of durationFormat's internal slot whose name is the Internal Slot value of the current row. + auto unit_options = (duration_format.*duration_instances_component.get_internal_slot)(); - // ii. Let value be the value of duration's field whose name is the Value Field value of the current row. - // iii. Set value to value / 10**exponent. - Crypto::BigFraction value { - Crypto::SignedBigInteger { (duration.*duration_instances_component.value_slot)() }, - Crypto::UnsignedBigInteger { pow(10, exponent) } - }; + // b. If unitOptions.[[Style]] is "fractional", then + if (unit_options.style == DurationFormat::ValueStyle::Fractional) { + // i. Let unit be the Unit value of the current row. + auto unit = duration_instances_component.unit; - // iv. Set result to result + value. - result = result + value; + // ii. Assert: IsFractionalSecondUnitName(unit) is true. + VERIFY(is_fractional_second_unit_name(unit)); + + // iii. Let value be the value of duration's field whose name is the Value Field value of the current row. + auto value = (duration.*duration_instances_component.value_slot)(); + + // iv. Set result to result + (value / 10**exponent). + result = result + Crypto::BigFraction { Crypto::SignedBigInteger { value }, Crypto::UnsignedBigInteger { pow(10, exponent) } }; // v. Set exponent to exponent + 3. exponent += 3; @@ -326,22 +326,19 @@ Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_for // 13.5.8 NextUnitFractional ( durationFormat, unit ), https://tc39.es/ecma402/#sec-nextunitfractional bool next_unit_fractional(DurationFormat const& duration_format, DurationFormat::Unit unit) { - // 1. Assert: unit is "seconds", "milliseconds", or "microseconds". - VERIFY(first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds)); - - // 2. If unit is "seconds" and durationFormat.[[MillisecondsStyle]] is "fractional", return true. - if (unit == DurationFormat::Unit::Seconds && duration_format.milliseconds_style() == DurationFormat::ValueStyle::Fractional) + // 1. If unit is "seconds" and durationFormat.[[MillisecondsOptions]].[[Style]] is "fractional", return true. + if (unit == DurationFormat::Unit::Seconds && duration_format.milliseconds_options().style == DurationFormat::ValueStyle::Fractional) return true; - // 3. Else if unit is "milliseconds" and durationFormat.[[MicrosecondsStyle]] is "fractional", return true. - if (unit == DurationFormat::Unit::Milliseconds && duration_format.microseconds_style() == DurationFormat::ValueStyle::Fractional) + // 2. If unit is "milliseconds" and durationFormat.[[MicrosecondsOptions]].[[Style]] is "fractional", return true. + if (unit == DurationFormat::Unit::Milliseconds && duration_format.microseconds_options().style == DurationFormat::ValueStyle::Fractional) return true; - // 4. Else if unit is "microseconds" and durationFormat.[[NanosecondsStyle]] is "fractional", return true. - if (unit == DurationFormat::Unit::Microseconds && duration_format.nanoseconds_style() == DurationFormat::ValueStyle::Fractional) + // 3. If unit is "microseconds" and durationFormat.[[NanosecondsOptions]].[[Style]] is "fractional", return true. + if (unit == DurationFormat::Unit::Microseconds && duration_format.nanoseconds_options().style == DurationFormat::ValueStyle::Fractional) return true; - // 5. Return false. + // 4. Return false. return false; } @@ -353,8 +350,8 @@ Vector format_numeric_hours(VM& vm, DurationFormat const& du // 1. Let result be a new empty List. Vector result; - // 2. Let hoursStyle be durationFormat.[[HoursStyle]]. - auto hours_style = duration_format.hours_style(); + // 2. Let hoursStyle be durationFormat.[[HoursOptions]].[[Style]]. + auto hours_style = duration_format.hours_options().style; // 3. Assert: hoursStyle is "numeric" or hoursStyle is "2-digit". VERIFY(hours_style == DurationFormat::ValueStyle::Numeric || hours_style == DurationFormat::ValueStyle::TwoDigit); @@ -418,8 +415,8 @@ Vector format_numeric_minutes(VM& vm, DurationFormat const& result.append({ .type = "literal"sv, .value = move(separator), .unit = {} }); } - // 3. Let minutesStyle be durationFormat.[[MinutesStyle]]. - auto minutes_style = duration_format.minutes_style(); + // 3. Let minutesStyle be durationFormat.[[MinutesOptions]].[[Style]]. + auto minutes_style = duration_format.minutes_options().style; // 4. Assert: minutesStyle is "numeric" or minutesStyle is "2-digit". VERIFY(minutes_style == DurationFormat::ValueStyle::Numeric || minutes_style == DurationFormat::ValueStyle::TwoDigit); @@ -483,8 +480,8 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& result.append({ .type = "literal"sv, .value = move(separator), .unit = {} }); } - // 3. Let secondsStyle be durationFormat.[[SecondsStyle]]. - auto seconds_style = duration_format.seconds_style(); + // 3. Let secondsStyle be durationFormat.[[SecondsOptions]].[[Style]]. + auto seconds_style = duration_format.seconds_options().style; // 4. Assert: secondsStyle is "numeric" or secondsStyle is "2-digit". VERIFY(seconds_style == DurationFormat::ValueStyle::Numeric || seconds_style == DurationFormat::ValueStyle::TwoDigit); @@ -567,14 +564,14 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 3. Let hoursValue be duration.[[Hours]]. auto hours_value = duration.hours(); - // 4. Let hoursDisplay be durationFormat.[[HoursDisplay]]. - auto hours_display = duration_format.hours_display(); + // 4. Let hoursDisplay be durationFormat.[[HoursOptions]].[[Display]]. + auto hours_display = duration_format.hours_options().display; - // 5. Let minutesValue be duration.[[Minutes]]. + // 5. Let minutesDisplay be durationFormat.[[MinutesOptions]].[[Display]]. auto minutes_value = duration.minutes(); // 6. Let minutesDisplay be durationFormat.[[MinutesDisplay]]. - auto minutes_display = duration_format.minutes_display(); + auto minutes_display = duration_format.minutes_options().display; // 7. Let secondsValue be duration.[[Seconds]]. Crypto::BigFraction seconds_value { duration.seconds() }; @@ -585,8 +582,8 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du seconds_value = seconds_value + compute_fractional_digits(duration_format, duration); } - // 9. Let secondsDisplay be durationFormat.[[SecondsDisplay]]. - auto seconds_display = duration_format.seconds_display(); + // 9. Let secondsDisplay be durationFormat.[[SecondsOptions]].[[Display]]. + auto seconds_display = duration_format.seconds_options().display; // 10. Let hoursFormatted be false. auto hours_formatted = false; @@ -682,7 +679,15 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du return numeric_parts_list; } -// 13.5.13 ListFormatParts ( durationFormat, partitionedPartsList ), https://tc39.es/ecma402/#sec-listformatparts +// 13.5.13 IsFractionalSecondUnitName ( unit ), https://tc39.es/ecma402/#sec-isfractionalsecondunitname +bool is_fractional_second_unit_name(DurationFormat::Unit unit) +{ + // 1. If unit is one of "milliseconds", "microseconds", or "nanoseconds", return true. + // 2. Return false. + return first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds); +} + +// 13.5.14 ListFormatParts ( durationFormat, partitionedPartsList ), https://tc39.es/ecma402/#sec-listformatparts Vector list_format_parts(VM& vm, DurationFormat const& duration_format, Vector>& partitioned_parts_list) { auto& realm = *vm.current_realm(); @@ -773,7 +778,7 @@ Vector list_format_parts(VM& vm, DurationFormat const& durat return flattened_parts_list; } -// 13.5.14 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/ecma402/#sec-partitiondurationformatpattern +// 13.5.15 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/ecma402/#sec-partitiondurationformatpattern // 15.9.8 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/proposal-temporal/#sec-formatnumericunits Vector partition_duration_format_pattern(VM& vm, DurationFormat const& duration_format, Temporal::Duration const& duration) { @@ -788,23 +793,25 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor // 3. Let numericUnitFound be false. auto numeric_unit_found = false; - // 4. While numericUnitFound is false, repeat for each row in Table 23 in table order, except the header row: + // 4. While numericUnitFound is false, repeat for each row in Table 24 in table order, except the header row: for (size_t i = 0; !numeric_unit_found && i < duration_instances_components.size(); ++i) { auto const& duration_instances_component = duration_instances_components[i]; // a. Let value be the value of duration's field whose name is the Value Field value of the current row. Crypto::BigFraction value { (duration.*duration_instances_component.value_slot)() }; - // b. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row. - auto style = (duration_format.*duration_instances_component.get_style_slot)(); + // b. Let unitOptions be the value of durationFormat's internal slot whose name is the Internal Slot value of the current row. + // c. Let style be unitOptions.[[Style]]. + // d. Let display be unitOptions.[[Display]]. + auto [style, display] = (duration_format.*duration_instances_component.get_internal_slot)(); - // c. Let display be the value of durationFormat's internal slot whose name is the Display Slot value of the current row. - auto display = (duration_format.*duration_instances_component.get_display_slot)(); - - // d. Let unit be the Unit value of the current row. + // e. Let unit be the Unit value of the current row. auto unit = duration_instances_component.unit; - // e. If style is "numeric" or "2-digit", then + // f. Let numberFormatUnit be the NumberFormat Unit value of the current row. + auto const& number_format_unit = unit_to_number_format_property_key(vm, duration_instances_component.unit); + + // g. If style is "numeric" or "2-digit", then if (style == DurationFormat::ValueStyle::Numeric || style == DurationFormat::ValueStyle::TwoDigit) { // i. Let numericPartsList be FormatNumericUnits(durationFormat, duration, unit, signDisplayed). auto numeric_parts_list = format_numeric_units(vm, duration_format, duration, unit, sign_displayed); @@ -816,13 +823,13 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor // iii. Set numericUnitFound to true. numeric_unit_found = true; } - // f. Else, + // h. Else, else { // i. Let nfOpts be OrdinaryObjectCreate(null). auto number_format_options = Object::create(realm, nullptr); - // ii. If unit is one of "seconds", "milliseconds", or "microseconds", and NextUnitFractional(durationFormat, unit) is true, then - if (first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds) && next_unit_fractional(duration_format, unit)) { + // ii. If NextUnitFractional(durationFormat, unit) is true, then + if (next_unit_fractional(duration_format, unit)) { // 1. Set value to value + ComputeFractionalDigits(durationFormat, duration). value = value + compute_fractional_digits(duration_format, duration); @@ -875,29 +882,26 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor MUST(number_format_options->create_data_property_or_throw(vm.names.signDisplay, PrimitiveString::create(vm, "never"sv))); } - // 4. Let numberFormatUnit be the NumberFormat Unit value of the current row. - auto const& number_format_unit = unit_to_number_format_property_key(vm, duration_instances_component.unit); - - // 5. Perform ! CreateDataPropertyOrThrow(nfOpts, "style", "unit"). + // 3. Perform ! CreateDataPropertyOrThrow(nfOpts, "style", "unit"). MUST(number_format_options->create_data_property_or_throw(vm.names.style, PrimitiveString::create(vm, "unit"sv))); - // 6. Perform ! CreateDataPropertyOrThrow(nfOpts, "unit", numberFormatUnit). + // 4. Perform ! CreateDataPropertyOrThrow(nfOpts, "unit", numberFormatUnit). MUST(number_format_options->create_data_property_or_throw(vm.names.unit, PrimitiveString::create(vm, number_format_unit.as_string()))); - // 7. Perform ! CreateDataPropertyOrThrow(nfOpts, "unitDisplay", style). + // 5. Perform ! CreateDataPropertyOrThrow(nfOpts, "unitDisplay", style). auto locale_style = Unicode::style_to_string(static_cast(style)); MUST(number_format_options->create_data_property_or_throw(vm.names.unitDisplay, PrimitiveString::create(vm, locale_style))); - // 8. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »). + // 6. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »). auto number_format = construct_number_format(vm, duration_format, number_format_options); - // 9. Let parts be PartitionNumberPattern(nf, value). + // 7. Let parts be PartitionNumberPattern(nf, value). auto parts = partition_number_pattern(number_format, value_mv); - // 10. Let list be a new empty List. + // 8. Let list be a new empty List. Vector list; - // 11. For each Record { [[Type]], [[Value]] } part of parts, do + // 10. For each Record { [[Type]], [[Value]] } part of parts, do list.ensure_capacity(parts.size()); for (auto& part : parts) { @@ -905,7 +909,7 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor list.unchecked_append({ .type = part.type, .value = move(part.value), .unit = number_format_unit.as_string() }); } - // 12. Append list to result. + // 11. Append list to result. result.append(list); } } diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.h b/Libraries/LibJS/Runtime/Intl/DurationFormat.h index 3445ede3429..8a1a2c88698 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.h +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.h @@ -66,6 +66,12 @@ public: Nanoseconds, }; + // 13.5.6.1 Duration Unit Options Records, https://tc39.es/ecma402/#sec-durationformat-unit-options-record + struct DurationUnitOptions { + ValueStyle style { ValueStyle::Long }; + Display display { Display::Auto }; + }; + static constexpr auto relevant_extension_keys() { // 13.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DurationFormat-internal-slots @@ -91,85 +97,35 @@ public: Style style() const { return m_style; } StringView style_string() const { return style_to_string(m_style); } - void set_years_style(ValueStyle years_style) { m_years_style = years_style; } - ValueStyle years_style() const { return m_years_style; } - StringView years_style_string() const { return value_style_to_string(m_years_style); } + void set_years_options(DurationUnitOptions years_options) { m_years_options = years_options; } + DurationUnitOptions years_options() const { return m_years_options; } - void set_years_display(Display years_display) { m_years_display = years_display; } - Display years_display() const { return m_years_display; } - StringView years_display_string() const { return display_to_string(m_years_display); } + void set_months_options(DurationUnitOptions months_options) { m_months_options = months_options; } + DurationUnitOptions months_options() const { return m_months_options; } - void set_months_style(ValueStyle months_style) { m_months_style = months_style; } - ValueStyle months_style() const { return m_months_style; } - StringView months_style_string() const { return value_style_to_string(m_months_style); } + void set_weeks_options(DurationUnitOptions weeks_options) { m_weeks_options = weeks_options; } + DurationUnitOptions weeks_options() const { return m_weeks_options; } - void set_months_display(Display months_display) { m_months_display = months_display; } - Display months_display() const { return m_months_display; } - StringView months_display_string() const { return display_to_string(m_months_display); } + void set_days_options(DurationUnitOptions days_options) { m_days_options = days_options; } + DurationUnitOptions days_options() const { return m_days_options; } - void set_weeks_style(ValueStyle weeks_style) { m_weeks_style = weeks_style; } - ValueStyle weeks_style() const { return m_weeks_style; } - StringView weeks_style_string() const { return value_style_to_string(m_weeks_style); } + void set_hours_options(DurationUnitOptions hours_options) { m_hours_options = hours_options; } + DurationUnitOptions hours_options() const { return m_hours_options; } - void set_weeks_display(Display weeks_display) { m_weeks_display = weeks_display; } - Display weeks_display() const { return m_weeks_display; } - StringView weeks_display_string() const { return display_to_string(m_weeks_display); } + void set_minutes_options(DurationUnitOptions minutes_options) { m_minutes_options = minutes_options; } + DurationUnitOptions minutes_options() const { return m_minutes_options; } - void set_days_style(ValueStyle days_style) { m_days_style = days_style; } - ValueStyle days_style() const { return m_days_style; } - StringView days_style_string() const { return value_style_to_string(m_days_style); } + void set_seconds_options(DurationUnitOptions seconds_options) { m_seconds_options = seconds_options; } + DurationUnitOptions seconds_options() const { return m_seconds_options; } - void set_days_display(Display days_display) { m_days_display = days_display; } - Display days_display() const { return m_days_display; } - StringView days_display_string() const { return display_to_string(m_days_display); } + void set_milliseconds_options(DurationUnitOptions milliseconds_options) { m_milliseconds_options = milliseconds_options; } + DurationUnitOptions milliseconds_options() const { return m_milliseconds_options; } - void set_hours_style(ValueStyle hours_style) { m_hours_style = hours_style; } - ValueStyle hours_style() const { return m_hours_style; } - StringView hours_style_string() const { return value_style_to_string(m_hours_style); } + void set_microseconds_options(DurationUnitOptions microseconds_options) { m_microseconds_options = microseconds_options; } + DurationUnitOptions microseconds_options() const { return m_microseconds_options; } - void set_hours_display(Display hours_display) { m_hours_display = hours_display; } - Display hours_display() const { return m_hours_display; } - StringView hours_display_string() const { return display_to_string(m_hours_display); } - - void set_minutes_style(ValueStyle minutes_style) { m_minutes_style = minutes_style; } - ValueStyle minutes_style() const { return m_minutes_style; } - StringView minutes_style_string() const { return value_style_to_string(m_minutes_style); } - - void set_minutes_display(Display minutes_display) { m_minutes_display = minutes_display; } - Display minutes_display() const { return m_minutes_display; } - StringView minutes_display_string() const { return display_to_string(m_minutes_display); } - - void set_seconds_style(ValueStyle seconds_style) { m_seconds_style = seconds_style; } - ValueStyle seconds_style() const { return m_seconds_style; } - StringView seconds_style_string() const { return value_style_to_string(m_seconds_style); } - - void set_seconds_display(Display seconds_display) { m_seconds_display = seconds_display; } - Display seconds_display() const { return m_seconds_display; } - StringView seconds_display_string() const { return display_to_string(m_seconds_display); } - - void set_milliseconds_style(ValueStyle milliseconds_style) { m_milliseconds_style = milliseconds_style; } - ValueStyle milliseconds_style() const { return m_milliseconds_style; } - StringView milliseconds_style_string() const { return value_style_to_string(m_milliseconds_style); } - - void set_milliseconds_display(Display milliseconds_display) { m_milliseconds_display = milliseconds_display; } - Display milliseconds_display() const { return m_milliseconds_display; } - StringView milliseconds_display_string() const { return display_to_string(m_milliseconds_display); } - - void set_microseconds_style(ValueStyle microseconds_style) { m_microseconds_style = microseconds_style; } - ValueStyle microseconds_style() const { return m_microseconds_style; } - StringView microseconds_style_string() const { return value_style_to_string(m_microseconds_style); } - - void set_microseconds_display(Display microseconds_display) { m_microseconds_display = microseconds_display; } - Display microseconds_display() const { return m_microseconds_display; } - StringView microseconds_display_string() const { return display_to_string(m_microseconds_display); } - - void set_nanoseconds_style(ValueStyle nanoseconds_style) { m_nanoseconds_style = nanoseconds_style; } - ValueStyle nanoseconds_style() const { return m_nanoseconds_style; } - StringView nanoseconds_style_string() const { return value_style_to_string(m_nanoseconds_style); } - - void set_nanoseconds_display(Display nanoseconds_display) { m_nanoseconds_display = nanoseconds_display; } - Display nanoseconds_display() const { return m_nanoseconds_display; } - StringView nanoseconds_display_string() const { return display_to_string(m_nanoseconds_display); } + void set_nanoseconds_options(DurationUnitOptions nanoseconds_options) { m_nanoseconds_options = nanoseconds_options; } + DurationUnitOptions nanoseconds_options() const { return m_nanoseconds_options; } void set_fractional_digits(Optional fractional_digits) { m_fractional_digits = move(fractional_digits); } bool has_fractional_digits() const { return m_fractional_digits.has_value(); } @@ -183,78 +139,62 @@ private: String m_hour_minute_separator; // [[HourMinutesSeparator]] String m_minute_second_separator; // [[MinutesSecondsSeparator]] - Style m_style { Style::Long }; // [[Style]] - ValueStyle m_years_style { ValueStyle::Long }; // [[YearsStyle]] - Display m_years_display { Display::Auto }; // [[YearsDisplay]] - ValueStyle m_months_style { ValueStyle::Long }; // [[MonthsStyle]] - Display m_months_display { Display::Auto }; // [[MonthsDisplay]] - ValueStyle m_weeks_style { ValueStyle::Long }; // [[WeeksStyle]] - Display m_weeks_display { Display::Auto }; // [[WeeksDisplay]] - ValueStyle m_days_style { ValueStyle::Long }; // [[DaysStyle]] - Display m_days_display { Display::Auto }; // [[DaysDisplay]] - ValueStyle m_hours_style { ValueStyle::Long }; // [[HoursStyle]] - Display m_hours_display { Display::Auto }; // [[HoursDisplay]] - ValueStyle m_minutes_style { ValueStyle::Long }; // [[MinutesStyle]] - Display m_minutes_display { Display::Auto }; // [[MinutesDisplay]] - ValueStyle m_seconds_style { ValueStyle::Long }; // [[SecondsStyle]] - Display m_seconds_display { Display::Auto }; // [[SecondsDisplay]] - ValueStyle m_milliseconds_style { ValueStyle::Long }; // [[MillisecondsStyle]] - Display m_milliseconds_display { Display::Auto }; // [[MillisecondsDisplay]] - ValueStyle m_microseconds_style { ValueStyle::Long }; // [[MicrosecondsStyle]] - Display m_microseconds_display { Display::Auto }; // [[MicrosecondsDisplay]] - ValueStyle m_nanoseconds_style { ValueStyle::Long }; // [[NanosecondsStyle]] - Display m_nanoseconds_display { Display::Auto }; // [[NanosecondsDisplay]] - Optional m_fractional_digits; // [[FractionalDigits]] + Style m_style { Style::Long }; // [[Style]] + DurationUnitOptions m_years_options; // [[YearsOptions]] + DurationUnitOptions m_months_options; // [[MonthsOptions]] + DurationUnitOptions m_weeks_options; // [[WeeksOptions]] + DurationUnitOptions m_days_options; // [[DaysOptions]] + DurationUnitOptions m_hours_options; // [[HoursOptions]] + DurationUnitOptions m_minutes_options; // [[MinutesOptions]] + DurationUnitOptions m_seconds_options; // [[SecondsOptions]] + DurationUnitOptions m_milliseconds_options; // [[MillisecondsOptions]] + DurationUnitOptions m_microseconds_options; // [[MicrosecondsOptions]] + DurationUnitOptions m_nanoseconds_options; // [[NanosecondsOptions]] + Optional m_fractional_digits; // [[FractionalDigits]] }; struct DurationInstanceComponent { double (Temporal::Duration::*value_slot)() const; - DurationFormat::ValueStyle (DurationFormat::*get_style_slot)() const; - void (DurationFormat::*set_style_slot)(DurationFormat::ValueStyle); - DurationFormat::Display (DurationFormat::*get_display_slot)() const; - void (DurationFormat::*set_display_slot)(DurationFormat::Display); + DurationFormat::DurationUnitOptions (DurationFormat::*get_internal_slot)() const; + void (DurationFormat::*set_internal_slot)(DurationFormat::DurationUnitOptions); DurationFormat::Unit unit; - ReadonlySpan values; + ReadonlySpan styles; DurationFormat::ValueStyle digital_default; }; // Table 20: Internal slots and property names of DurationFormat instances relevant to Intl.DurationFormat constructor, https://tc39.es/ecma402/#table-durationformat -// Table 23: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/ecma402/#table-partition-duration-format-pattern -static constexpr auto date_values = AK::Array { "long"sv, "short"sv, "narrow"sv }; -static constexpr auto time_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv }; -static constexpr auto sub_second_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv }; +// Table 24: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/ecma402/#table-partition-duration-format-pattern +static constexpr auto date_styles = AK::Array { "long"sv, "short"sv, "narrow"sv }; +static constexpr auto time_styles = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv }; +static constexpr auto sub_second_styles = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv }; static constexpr auto duration_instances_components = to_array({ - { &Temporal::Duration::years, &DurationFormat::years_style, &DurationFormat::set_years_style, &DurationFormat::years_display, &DurationFormat::set_years_display, DurationFormat::Unit::Years, date_values, DurationFormat::ValueStyle::Short }, - { &Temporal::Duration::months, &DurationFormat::months_style, &DurationFormat::set_months_style, &DurationFormat::months_display, &DurationFormat::set_months_display, DurationFormat::Unit::Months, date_values, DurationFormat::ValueStyle::Short }, - { &Temporal::Duration::weeks, &DurationFormat::weeks_style, &DurationFormat::set_weeks_style, &DurationFormat::weeks_display, &DurationFormat::set_weeks_display, DurationFormat::Unit::Weeks, date_values, DurationFormat::ValueStyle::Short }, - { &Temporal::Duration::days, &DurationFormat::days_style, &DurationFormat::set_days_style, &DurationFormat::days_display, &DurationFormat::set_days_display, DurationFormat::Unit::Days, date_values, DurationFormat::ValueStyle::Short }, - { &Temporal::Duration::hours, &DurationFormat::hours_style, &DurationFormat::set_hours_style, &DurationFormat::hours_display, &DurationFormat::set_hours_display, DurationFormat::Unit::Hours, time_values, DurationFormat::ValueStyle::Numeric }, - { &Temporal::Duration::minutes, &DurationFormat::minutes_style, &DurationFormat::set_minutes_style, &DurationFormat::minutes_display, &DurationFormat::set_minutes_display, DurationFormat::Unit::Minutes, time_values, DurationFormat::ValueStyle::Numeric }, - { &Temporal::Duration::seconds, &DurationFormat::seconds_style, &DurationFormat::set_seconds_style, &DurationFormat::seconds_display, &DurationFormat::set_seconds_display, DurationFormat::Unit::Seconds, time_values, DurationFormat::ValueStyle::Numeric }, - { &Temporal::Duration::milliseconds, &DurationFormat::milliseconds_style, &DurationFormat::set_milliseconds_style, &DurationFormat::milliseconds_display, &DurationFormat::set_milliseconds_display, DurationFormat::Unit::Milliseconds, sub_second_values, DurationFormat::ValueStyle::Numeric }, - { &Temporal::Duration::microseconds, &DurationFormat::microseconds_style, &DurationFormat::set_microseconds_style, &DurationFormat::microseconds_display, &DurationFormat::set_microseconds_display, DurationFormat::Unit::Microseconds, sub_second_values, DurationFormat::ValueStyle::Numeric }, - { &Temporal::Duration::nanoseconds, &DurationFormat::nanoseconds_style, &DurationFormat::set_nanoseconds_style, &DurationFormat::nanoseconds_display, &DurationFormat::set_nanoseconds_display, DurationFormat::Unit::Nanoseconds, sub_second_values, DurationFormat::ValueStyle::Numeric }, + { &Temporal::Duration::years, &DurationFormat::years_options, &DurationFormat::set_years_options, DurationFormat::Unit::Years, date_styles, DurationFormat::ValueStyle::Short }, + { &Temporal::Duration::months, &DurationFormat::months_options, &DurationFormat::set_months_options, DurationFormat::Unit::Months, date_styles, DurationFormat::ValueStyle::Short }, + { &Temporal::Duration::weeks, &DurationFormat::weeks_options, &DurationFormat::set_weeks_options, DurationFormat::Unit::Weeks, date_styles, DurationFormat::ValueStyle::Short }, + { &Temporal::Duration::days, &DurationFormat::days_options, &DurationFormat::set_days_options, DurationFormat::Unit::Days, date_styles, DurationFormat::ValueStyle::Short }, + { &Temporal::Duration::hours, &DurationFormat::hours_options, &DurationFormat::set_hours_options, DurationFormat::Unit::Hours, time_styles, DurationFormat::ValueStyle::Numeric }, + { &Temporal::Duration::minutes, &DurationFormat::minutes_options, &DurationFormat::set_minutes_options, DurationFormat::Unit::Minutes, time_styles, DurationFormat::ValueStyle::Numeric }, + { &Temporal::Duration::seconds, &DurationFormat::seconds_options, &DurationFormat::set_seconds_options, DurationFormat::Unit::Seconds, time_styles, DurationFormat::ValueStyle::Numeric }, + { &Temporal::Duration::milliseconds, &DurationFormat::milliseconds_options, &DurationFormat::set_milliseconds_options, DurationFormat::Unit::Milliseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric }, + { &Temporal::Duration::microseconds, &DurationFormat::microseconds_options, &DurationFormat::set_microseconds_options, DurationFormat::Unit::Microseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric }, + { &Temporal::Duration::nanoseconds, &DurationFormat::nanoseconds_options, &DurationFormat::set_nanoseconds_options, DurationFormat::Unit::Nanoseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric }, }); -struct DurationUnitOptions { - DurationFormat::ValueStyle style; - DurationFormat::Display display; -}; - struct DurationFormatPart { StringView type; String value; StringView unit; }; -ThrowCompletionOr get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan styles_list, DurationFormat::ValueStyle digital_base, Optional previous_style, bool two_digit_hours); +ThrowCompletionOr get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan styles_list, DurationFormat::ValueStyle digital_base, Optional previous_style, bool two_digit_hours); Crypto::BigFraction compute_fractional_digits(DurationFormat const&, Temporal::Duration const&); bool next_unit_fractional(DurationFormat const&, DurationFormat::Unit unit); Vector format_numeric_hours(VM&, DurationFormat const&, MathematicalValue const& hours_value, bool sign_displayed); Vector format_numeric_minutes(VM&, DurationFormat const&, MathematicalValue const& minutes_value, bool hours_displayed, bool sign_displayed); Vector format_numeric_seconds(VM&, DurationFormat const&, MathematicalValue const& seconds_value, bool minutes_displayed, bool sign_displayed); Vector format_numeric_units(VM&, DurationFormat const&, Temporal::Duration const&, DurationFormat::Unit first_numeric_unit, bool sign_displayed); +bool is_fractional_second_unit_name(DurationFormat::Unit); Vector list_format_parts(VM&, DurationFormat const&, Vector>& partitioned_parts_list); Vector partition_duration_format_pattern(VM&, DurationFormat const&, Temporal::Duration const&); diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp index d5513cb0b45..c74beee2753 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp @@ -53,7 +53,7 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function auto locales = vm.argument(0); auto options_value = vm.argument(1); - // 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[NumberingSystem]], [[Style]], [[YearsStyle]], [[YearsDisplay]], [[MonthsStyle]], [[MonthsDisplay]], [[WeeksStyle]], [[WeeksDisplay]], [[DaysStyle]], [[DaysDisplay]], [[HoursStyle]], [[HoursDisplay]], [[MinutesStyle]], [[MinutesDisplay]], [[SecondsStyle]], [[SecondsDisplay]], [[MillisecondsStyle]], [[MillisecondsDisplay]], [[MicrosecondsStyle]], [[MicrosecondsDisplay]], [[NanosecondsStyle]], [[NanosecondsDisplay]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »). + // 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[NumberingSystem]], [[Style]], [[YearsOptions]], [[MonthsOptions]], [[WeeksOptions]], [[DaysOptions]], [[HoursOptions]], [[MinutesOptions]], [[SecondsOptions]], [[MillisecondsOptions]], [[MicrosecondsOptions]], [[NanosecondsOptions]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »). auto duration_format = TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::intl_duration_format_prototype)); // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). @@ -112,31 +112,25 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function // 19. For each row of Table 20, except the header row, in table order, do for (auto const& duration_instances_component : duration_instances_components) { - // a. Let styleSlot be the Style Slot value of the current row. - auto style_slot = duration_instances_component.set_style_slot; + // a. Let slot be the Internal Slot value of the current row. + auto slot = duration_instances_component.set_internal_slot; - // b. Let displaySlot be the Display Slot value of the current row. - auto display_slot = duration_instances_component.set_display_slot; - - // c. Let unit be the Unit value of the current row. + // b. Let unit be the Unit value of the current row. auto unit = duration_instances_component.unit; - // d. Let valueList be the Values value of the current row. - auto value_list = duration_instances_component.values; + // c. Let styles be the Styles value of the current row. + auto styles = duration_instances_component.styles; - // e. Let digitalBase be the Digital Default value of the current row. + // d. Let digitalBase be the Digital Default value of the current row. auto digital_base = duration_instances_component.digital_default; - // f. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, valueList, digitalBase, prevStyle, digitalFormat.[[TwoDigitHours]]). - auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style(), value_list, digital_base, previous_style, digital_format.uses_two_digit_hours)); + // e. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, styles, digitalBase, prevStyle, digitalFormat.[[TwoDigitHours]]). + auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style(), styles, digital_base, previous_style, digital_format.uses_two_digit_hours)); - // g. Set the value of the styleSlot slot of durationFormat to unitOptions.[[Style]]. - (duration_format->*style_slot)(unit_options.style); + // f. Set the value of durationFormat's internal slot whose name is slot to unitOptions. + (duration_format->*slot)(unit_options); - // h. Set the value of the displaySlot slot of durationFormat to unitOptions.[[Display]]. - (duration_format->*display_slot)(unit_options.display); - - // i. If unit is one of "hours", "minutes", "seconds", "milliseconds", or "microseconds", then + // g. If unit is one of "hours", "minutes", "seconds", "milliseconds", or "microseconds", then if (first_is_one_of(unit, DurationFormat::Unit::Hours, DurationFormat::Unit::Minutes, DurationFormat::Unit::Seconds, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds)) { // i. Set prevStyle to unitOptions.[[Style]]. previous_style = unit_options.style; diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp index 67f78c21010..1d8e5b2eb3a 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp @@ -50,49 +50,57 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::resolved_options) auto options = Object::create(realm, realm.intrinsics().object_prototype()); // 4. For each row of Table 21, except the header row, in table order, do - auto create_option = [&](PropertyKey const& property, StringView value) { + auto create_option = [&](PropertyKey const& property, Optional display_property, T value) { // a. Let p be the Property value of the current row. // b. Let v be the value of df's internal slot whose name is the Internal Slot value of the current row. - // c. If p is "fractionalDigits", then - // i. If v is not undefined, perform ! CreateDataPropertyOrThrow(options, p, 𝔽(v)). - // NOTE: This case is handled separately below. + // c. If v is not undefined, then + // i. If there is a Conversion value in the current row, let conversion be that value; else let conversion be empty. + // ii. If conversion is number, then + // 1. Set v to 𝔽(v). + // NOTE: This case is for fractionalDigits and is handled separately below. - // d. Else, - // i. Assert: v is not undefined. - // ii. If v is "fractional", then - if (value == "fractional"sv) { - // 1. Assert: The Internal Slot value of the current row is [[MillisecondsStyle]], [[MicrosecondsStyle]], or [[NanosecondsStyle]] . - // 2. Set v to "numeric". - value = "numeric"sv; + // iii. Else if conversion is not empty, then + if constexpr (IsSame) { + // 1. Assert: conversion is STYLE+DISPLAY and v is a Duration Unit Options Record. + // 2. NOTE: v.[[Style]] will be represented with a property named p (a plural Temporal unit), then v.[[Display]] will be represented with a property whose name suffixes p with "Display". + VERIFY(display_property.has_value()); + + // 3. Let style be v.[[Style]]. + auto style = value.style; + + // 4. If style is "fractional", then + if (style == DurationFormat::ValueStyle::Fractional) { + // a. Assert: IsFractionalSecondUnitName(p) is true. + // b. Set style to "numeric". + style = DurationFormat::ValueStyle::Numeric; + } + + // 5. Perform ! CreateDataPropertyOrThrow(options, p, style). + MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, DurationFormat::value_style_to_string(style)))); + + // 6. Set p to the string-concatenation of p and "Display". + // 7. Set v to v.[[Display]]. + MUST(options->create_data_property_or_throw(*display_property, PrimitiveString::create(vm, DurationFormat::display_to_string(value.display)))); + } else { + // iv. Perform ! CreateDataPropertyOrThrow(options, p, v). + MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, move(value)))); } - // iii. Perform ! CreateDataPropertyOrThrow(options, p, v). - MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, value))); }; - create_option(vm.names.locale, duration_format->locale()); - create_option(vm.names.numberingSystem, duration_format->numbering_system()); - create_option(vm.names.style, duration_format->style_string()); - create_option(vm.names.years, duration_format->years_style_string()); - create_option(vm.names.yearsDisplay, duration_format->years_display_string()); - create_option(vm.names.months, duration_format->months_style_string()); - create_option(vm.names.monthsDisplay, duration_format->months_display_string()); - create_option(vm.names.weeks, duration_format->weeks_style_string()); - create_option(vm.names.weeksDisplay, duration_format->weeks_display_string()); - create_option(vm.names.days, duration_format->days_style_string()); - create_option(vm.names.daysDisplay, duration_format->days_display_string()); - create_option(vm.names.hours, duration_format->hours_style_string()); - create_option(vm.names.hoursDisplay, duration_format->hours_display_string()); - create_option(vm.names.minutes, duration_format->minutes_style_string()); - create_option(vm.names.minutesDisplay, duration_format->minutes_display_string()); - create_option(vm.names.seconds, duration_format->seconds_style_string()); - create_option(vm.names.secondsDisplay, duration_format->seconds_display_string()); - create_option(vm.names.milliseconds, duration_format->milliseconds_style_string()); - create_option(vm.names.millisecondsDisplay, duration_format->milliseconds_display_string()); - create_option(vm.names.microseconds, duration_format->microseconds_style_string()); - create_option(vm.names.microsecondsDisplay, duration_format->microseconds_display_string()); - create_option(vm.names.nanoseconds, duration_format->nanoseconds_style_string()); - create_option(vm.names.nanosecondsDisplay, duration_format->nanoseconds_display_string()); + create_option(vm.names.locale, {}, duration_format->locale()); + create_option(vm.names.numberingSystem, {}, duration_format->numbering_system()); + create_option(vm.names.style, {}, duration_format->style_string()); + create_option(vm.names.years, vm.names.yearsDisplay, duration_format->years_options()); + create_option(vm.names.months, vm.names.monthsDisplay, duration_format->months_options()); + create_option(vm.names.weeks, vm.names.weeksDisplay, duration_format->weeks_options()); + create_option(vm.names.days, vm.names.daysDisplay, duration_format->days_options()); + create_option(vm.names.hours, vm.names.hoursDisplay, duration_format->hours_options()); + create_option(vm.names.minutes, vm.names.minutesDisplay, duration_format->minutes_options()); + create_option(vm.names.seconds, vm.names.secondsDisplay, duration_format->seconds_options()); + create_option(vm.names.milliseconds, vm.names.millisecondsDisplay, duration_format->milliseconds_options()); + create_option(vm.names.microseconds, vm.names.microsecondsDisplay, duration_format->microseconds_options()); + create_option(vm.names.nanoseconds, vm.names.nanosecondsDisplay, duration_format->nanoseconds_options()); if (duration_format->has_fractional_digits()) MUST(options->create_data_property_or_throw(vm.names.fractionalDigits, Value(duration_format->fractional_digits()))); diff --git a/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp b/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp index 9f5c8360043..2c7c1c7e111 100644 --- a/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp @@ -47,7 +47,7 @@ JS_DEFINE_NATIVE_FUNCTION(ListFormatPrototype::resolved_options) // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). auto options = Object::create(realm, realm.intrinsics().object_prototype()); - // 4. For each row of Table 24, except the header row, in table order, do + // 4. For each row of Table 25, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. Let v be the value of lf's internal slot whose name is the Internal Slot value of the current row. // c. Assert: v is not undefined. diff --git a/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp b/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp index 2d870783831..ff54ee51c00 100644 --- a/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp @@ -53,7 +53,7 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatPrototype::resolved_options) // 4. Let options be OrdinaryObjectCreate(%Object.prototype%). auto options = Object::create(realm, realm.intrinsics().object_prototype()); - // 5. For each row of Table 25, except the header row, in table order, do + // 5. For each row of Table 26, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. Let v be the value of nf's internal slot whose name is the Internal Slot value of the current row. // c. If v is not undefined, then diff --git a/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp b/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp index d72232d42a1..30e54c66e24 100644 --- a/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp @@ -56,7 +56,7 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options) return PrimitiveString::create(vm, Unicode::plural_category_to_string(category)); }); - // 5. For each row of Table 29, except the header row, in table order, do + // 5. For each row of Table 30, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. If p is "pluralCategories", then // i. Let v be CreateArrayFromList(pluralCategories). diff --git a/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp b/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp index 20f616dea94..71d66d1b540 100644 --- a/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp @@ -46,7 +46,7 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::resolved_options) // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). auto options = Object::create(realm, realm.intrinsics().object_prototype()); - // 4. For each row of Table 30, except the header row, in table order, do + // 4. For each row of Table 31, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. Let v be the value of relativeTimeFormat's internal slot whose name is the Internal Slot value of the current row. // c. Assert: v is not undefined. diff --git a/Libraries/LibJS/Runtime/Intl/SegmenterPrototype.cpp b/Libraries/LibJS/Runtime/Intl/SegmenterPrototype.cpp index 79301a918ec..051303c5ff1 100644 --- a/Libraries/LibJS/Runtime/Intl/SegmenterPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/SegmenterPrototype.cpp @@ -45,7 +45,7 @@ JS_DEFINE_NATIVE_FUNCTION(SegmenterPrototype::resolved_options) // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). auto options = Object::create(realm, realm.intrinsics().object_prototype()); - // 4. For each row of Table 31, except the header row, in table order, do + // 4. For each row of Table 32, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. Let v be the value of segmenter's internal slot whose name is the Internal Slot value of the current row. // c. Assert: v is not undefined. From a457ebeec50081686cb59a335bbf2e9f34cfcda6 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 19 Mar 2025 01:47:23 +1300 Subject: [PATCH 025/141] LibWeb/HTML: Implement 'convert string to number' for datetime-local --- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 26 +++++++++++++++++++ .../the-input-element/input-valueasnumber.txt | 8 +++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index ac12a592431..8e02be0eb50 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -2308,6 +2308,29 @@ static String convert_number_to_time_string(double input) return MUST(String::formatted("{:02d}:{:02d}", JS::hour_from_time(input), JS::min_from_time(input))); } +// https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):concept-input-value-number-string +static String convert_number_to_local_date_and_time_string(double input) +{ + // The algorithm to convert a number to a string, given a number input, is as follows: Return a valid + // normalized local date and time string that represents the date and time that is input milliseconds + // after midnight on the morning of 1970-01-01 (the time represented by the value "1970-01-01T00:00:00.0"). + auto year = JS::year_from_time(input); + auto month = JS::month_from_time(input) + 1; // Adjust for zero-based month + auto day = JS::date_from_time(input); + auto hour = JS::hour_from_time(input); + auto minutes = JS::min_from_time(input); + auto seconds = JS::sec_from_time(input); + auto milliseconds = JS::ms_from_time(input); + + if (seconds > 0) { + if (milliseconds > 0) + return MUST(String::formatted("{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}.{:03d}", year, month, day, hour, minutes, seconds, milliseconds)); + return MUST(String::formatted("{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}", year, month, day, hour, minutes, seconds)); + } + + return MUST(String::formatted("{:04d}-{:02d}-{:02d}T{:02d}:{:02d}", year, month, day, hour, minutes)); +} + // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number String HTMLInputElement::convert_number_to_string(double input) const { @@ -2331,6 +2354,9 @@ String HTMLInputElement::convert_number_to_string(double input) const if (type_state() == TypeAttributeState::Time) return convert_number_to_time_string(input); + if (type_state() == TypeAttributeState::LocalDateAndTime) + return convert_number_to_local_date_and_time_string(input); + dbgln("HTMLInputElement::convert_number_to_string() not implemented for input type {}", type()); return {}; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt index 34d9e60bbfb..edde99ed0dc 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt @@ -2,8 +2,8 @@ Harness status: OK Found 60 tests -54 Pass -6 Fail +56 Pass +4 Fail Pass valueAsNumber getter on type date (actual value: , expected valueAsNumber: NaN) Pass valueAsNumber getter on type date (actual value: 0000-12-10, expected valueAsNumber: NaN) Pass valueAsNumber getter on type date (actual value: 2019-00-12, expected valueAsNumber: NaN) @@ -43,8 +43,8 @@ Pass valueAsNumber setter on type time (actual valueAsNumber: 86340000, expected Pass valueAsNumber getter on type datetime-local (actual value: , expected valueAsNumber: NaN) Fail valueAsNumber getter on type datetime-local (actual value: 2019-12-10T00:00, expected valueAsNumber: 1575936000000) Fail valueAsNumber getter on type datetime-local (actual value: 2019-12-10T12:00, expected valueAsNumber: 1575979200000) -Fail valueAsNumber setter on type datetime-local (actual valueAsNumber: 1575936000000, expected value: 2019-12-10T00:00) -Fail valueAsNumber setter on type datetime-local (actual valueAsNumber: 1575979200000, expected value: 2019-12-10T12:00) +Pass valueAsNumber setter on type datetime-local (actual valueAsNumber: 1575936000000, expected value: 2019-12-10T00:00) +Pass valueAsNumber setter on type datetime-local (actual valueAsNumber: 1575979200000, expected value: 2019-12-10T12:00) Pass valueAsNumber getter on type number (actual value: , expected valueAsNumber: NaN) Pass valueAsNumber getter on type number (actual value: 123, expected valueAsNumber: 123) Pass valueAsNumber getter on type number (actual value: 123.456, expected valueAsNumber: 123.456) From 894c51e8e7fe24e4bc260f8d1f10e267b5c95473 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 19 Mar 2025 01:53:26 +1300 Subject: [PATCH 026/141] LibWeb/HTML: Handle missing second component in datetime-local We were previously not checking for EOF which meant we were not handling seconds being missing in the time component. --- Libraries/LibWeb/HTML/Dates.cpp | 73 +++++++++++-------- .../HTML/HTMLInputElement-valueAsNumber.txt | 2 +- .../the-input-element/input-valueasnumber.txt | 8 +- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/Libraries/LibWeb/HTML/Dates.cpp b/Libraries/LibWeb/HTML/Dates.cpp index 0e3870515f0..574c14f9246 100644 --- a/Libraries/LibWeb/HTML/Dates.cpp +++ b/Libraries/LibWeb/HTML/Dates.cpp @@ -414,13 +414,16 @@ static Optional parse_a_time_component(GenericLexer& input) if (!maybe_hour.has_value()) return {}; auto hour = maybe_hour.value(); + // 2. If hour is not a number in the range 0 ≤ hour ≤ 23, then fail. if (hour < 0 || hour > 23) return {}; + // 3. If position is beyond the end of input or if the character at position is not a U+003A COLON character, then // fail. Otherwise, move position forwards one character. if (!input.consume_specific(':')) return {}; + // 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence // is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. // Let that number be the minute. @@ -431,44 +434,50 @@ static Optional parse_a_time_component(GenericLexer& input) if (!maybe_minute.has_value()) return {}; auto minute = maybe_minute.value(); + // 5. If minute is not a number in the range 0 ≤ minute ≤ 59, then fail. if (minute < 0 || hour > 59) return {}; + // 6. Let second be 0. i32 second = 0; + // 7. If position is not beyond the end of input and the character at position is U+003A (:), then: - if (!input.consume_specific(':')) - return {}; - // 7.1. Advance position to the next character in input. - // 7.2. If position is beyond the end of input, or at the last character in input, or if the next two characters in - // input starting at position are not both ASCII digits, then fail. - if (input.is_eof() || input.tell_remaining() == 1 || (!is_ascii_digit(input.peek()) && !is_ascii_digit(input.peek(1)))) - return {}; - // 7.3. Collect a sequence of code points that are either ASCII digits or U+002E FULL STOP characters from input - // given position. - auto second_string = input.consume_while([](auto ch) { return is_ascii_digit(ch) || ch == '.'; }); - // If the collected sequence is three characters long, or if it is longer than three characters long and the third - // character is not a U+002E FULL STOP character, or if it has more than one U+002E FULL STOP character, then fail. - if (second_string.length() == 3) - return {}; - if (second_string.length() > 3 && second_string[2] != '.') - return {}; - if (second_string.find_all("."sv).size() > 1) - return {}; - // Otherwise, interpret the resulting sequence as a base-ten number (possibly with a fractional part). Set second - // to that number. - // NB: The spec doesn't state requirements for what we must do with the fractional part of the second(s) string. - // The spec neither requires that we separately preserve it nor requires that we completely discard it. If we - // did have any reason at all to preserve it, we could parse the string into a float here. But there doesn't - // seem to be any point in doing that, because there’s nothing in the corresponding calling algorithm(s) in the - // spec that takes a milliseconds field and does anything with it anyway. - auto maybe_second = second_string.to_number(); - if (!maybe_second.has_value()) - return {}; - second = maybe_second.value(); - // 7.4. If second is not a number in the range 0 ≤ second < 60, then fail. - if (second < 0 || second > 60) - return {}; + if (input.consume_specific(':')) { + // 1. Advance position to the next character in input. + // 2. If position is beyond the end of input, or at the last character in input, or if the next two characters in + // input starting at position are not both ASCII digits, then fail. + if (input.is_eof() || input.tell_remaining() == 1 || (!is_ascii_digit(input.peek()) && !is_ascii_digit(input.peek(1)))) + return {}; + + // 3. Collect a sequence of code points that are either ASCII digits or U+002E FULL STOP characters from input + // given position. + auto second_string = input.consume_while([](auto ch) { return is_ascii_digit(ch) || ch == '.'; }); + // If the collected sequence is three characters long, or if it is longer than three characters long and the third + // character is not a U+002E FULL STOP character, or if it has more than one U+002E FULL STOP character, then fail. + if (second_string.length() == 3) + return {}; + if (second_string.length() > 3 && second_string[2] != '.') + return {}; + if (second_string.find_all("."sv).size() > 1) + return {}; + // Otherwise, interpret the resulting sequence as a base-ten number (possibly with a fractional part). Set second + // to that number. + // NB: The spec doesn't state requirements for what we must do with the fractional part of the second(s) string. + // The spec neither requires that we separately preserve it nor requires that we completely discard it. If we + // did have any reason at all to preserve it, we could parse the string into a float here. But there doesn't + // seem to be any point in doing that, because there’s nothing in the corresponding calling algorithm(s) in the + // spec that takes a milliseconds field and does anything with it anyway. + auto maybe_second = second_string.to_number(); + if (!maybe_second.has_value()) + return {}; + second = maybe_second.value(); + + // 4. If second is not a number in the range 0 ≤ second < 60, then fail. + if (second < 0 || second > 60) + return {}; + } + // 8. Return hour, minute, and second. return HourMinuteSecond { hour, minute, second }; } diff --git a/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt b/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt index ae462d52f55..77921fc7266 100644 --- a/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt +++ b/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt @@ -35,7 +35,7 @@ date did not throw: 0 month did not throw: 100 week did not throw: 345600000 time did not throw: 0 -datetime-local did not throw: NaN +datetime-local did not throw: 0 color threw exception: InvalidStateError: valueAsNumber: Invalid input type used checkbox threw exception: InvalidStateError: valueAsNumber: Invalid input type used radio threw exception: InvalidStateError: valueAsNumber: Invalid input type used diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt index edde99ed0dc..e85265e8ccd 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt @@ -2,8 +2,8 @@ Harness status: OK Found 60 tests -56 Pass -4 Fail +58 Pass +2 Fail Pass valueAsNumber getter on type date (actual value: , expected valueAsNumber: NaN) Pass valueAsNumber getter on type date (actual value: 0000-12-10, expected valueAsNumber: NaN) Pass valueAsNumber getter on type date (actual value: 2019-00-12, expected valueAsNumber: NaN) @@ -41,8 +41,8 @@ Pass valueAsNumber setter on type time (actual valueAsNumber: 0, expected value: Pass valueAsNumber setter on type time (actual valueAsNumber: 43200000, expected value: 12:00) Pass valueAsNumber setter on type time (actual valueAsNumber: 86340000, expected value: 23:59) Pass valueAsNumber getter on type datetime-local (actual value: , expected valueAsNumber: NaN) -Fail valueAsNumber getter on type datetime-local (actual value: 2019-12-10T00:00, expected valueAsNumber: 1575936000000) -Fail valueAsNumber getter on type datetime-local (actual value: 2019-12-10T12:00, expected valueAsNumber: 1575979200000) +Pass valueAsNumber getter on type datetime-local (actual value: 2019-12-10T00:00, expected valueAsNumber: 1575936000000) +Pass valueAsNumber getter on type datetime-local (actual value: 2019-12-10T12:00, expected valueAsNumber: 1575979200000) Pass valueAsNumber setter on type datetime-local (actual valueAsNumber: 1575936000000, expected value: 2019-12-10T00:00) Pass valueAsNumber setter on type datetime-local (actual valueAsNumber: 1575979200000, expected value: 2019-12-10T12:00) Pass valueAsNumber getter on type number (actual value: , expected valueAsNumber: NaN) From 101a8aef2646e634e1d5cca2642b68ba49c5c4f0 Mon Sep 17 00:00:00 2001 From: Alec Murphy Date: Tue, 18 Mar 2025 11:38:18 -0400 Subject: [PATCH 027/141] LibWeb: Use Super on macOS for page scroll/nav On macOS, we should use the Cmd (Super) modifier key along with the arrow keys to scroll to the beginning/end of the document, or navigate back and forth in the history, rather than the Ctrl or Alt keys. --- Libraries/LibWeb/Page/EventHandler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index ce3ba0e0ce9..57a5739fd3b 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -1272,7 +1272,7 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u switch (key) { case UIEvents::KeyCode::Key_Up: case UIEvents::KeyCode::Key_Down: - if (modifiers && modifiers != UIEvents::KeyModifier::Mod_Ctrl) + if (modifiers && modifiers != UIEvents::KeyModifier::Mod_PlatformCtrl) break; if (modifiers) key == UIEvents::KeyCode::Key_Up ? document->scroll_to_the_beginning_of_the_document() : document->window()->scroll_by(0, INT64_MAX); @@ -1281,7 +1281,11 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u return EventResult::Handled; case UIEvents::KeyCode::Key_Left: case UIEvents::KeyCode::Key_Right: +#if defined(AK_OS_MACOS) + if (modifiers && modifiers != UIEvents::KeyModifier::Mod_Super) +#else if (modifiers && modifiers != UIEvents::KeyModifier::Mod_Alt) +#endif break; if (modifiers) document->page().traverse_the_history_by_delta(key == UIEvents::KeyCode::Key_Left ? -1 : 1); From 394073f61161e950b855f33ffa69a9698be5482e Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 18 Mar 2025 17:37:32 +0100 Subject: [PATCH 028/141] LibWeb: Rename internals.signalTextTestIsDone() to signalTestIsDone() In upcoming change this function will be used for ref-tests as well. --- Libraries/LibWeb/Internals/Internals.cpp | 4 ++-- Libraries/LibWeb/Internals/Internals.h | 2 +- Libraries/LibWeb/Internals/Internals.idl | 2 +- Libraries/LibWeb/Page/Page.h | 2 +- Libraries/LibWebView/ViewImplementation.h | 2 +- Libraries/LibWebView/WebContentClient.cpp | 6 +++--- Libraries/LibWebView/WebContentClient.h | 2 +- Services/WebContent/PageClient.cpp | 4 ++-- Services/WebContent/PageClient.h | 2 +- Services/WebContent/WebContentClient.ipc | 2 +- .../document-write-flush-character-insertions.html | 2 +- ...-loading-not-blocked-by-img-outside-viewport.html | 2 +- .../hit-testing-an-xml-svg-should-not-crash.svg | 2 +- Tests/LibWeb/Text/input/include.js | 4 ++-- Tests/LibWeb/Text/input/module-script-in-head.html | 2 +- .../css/selectors/has-sibling-chrome-crash.html | 2 +- .../nth-of-namespace-class-invalidation-crash.html | 2 +- .../innertext-domnoderemoved-crash.html | 2 +- .../input/wpt-import/resources/testharnessreport.js | 2 +- UI/Headless/Test.cpp | 12 ++++++------ 20 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 88defe719c7..797ff8bf483 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -48,9 +48,9 @@ Page& Internals::internals_page() const return internals_window().page(); } -void Internals::signal_text_test_is_done(String const& text) +void Internals::signal_test_is_done(String const& text) { - internals_page().client().page_did_finish_text_test(text); + internals_page().client().page_did_finish_test(text); } void Internals::set_test_timeout(double milliseconds) diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index 709763e0dff..f7872be8730 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -20,7 +20,7 @@ class Internals final : public Bindings::PlatformObject { public: virtual ~Internals() override; - void signal_text_test_is_done(String const& text); + void signal_test_is_done(String const& text); void set_test_timeout(double milliseconds); void gc(); diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl index bdc411ec3bc..27ad7e3c2fe 100644 --- a/Libraries/LibWeb/Internals/Internals.idl +++ b/Libraries/LibWeb/Internals/Internals.idl @@ -5,7 +5,7 @@ [Exposed=Nobody] interface Internals { - undefined signalTextTestIsDone(DOMString text); + undefined signalTestIsDone(DOMString text); undefined setTestTimeout(double milliseconds); undefined gc(); diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 8be7d715384..ef834ada7c7 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -385,7 +385,7 @@ public: virtual void page_did_request_file_picker([[maybe_unused]] HTML::FileFilter const& accepted_file_types, Web::HTML::AllowMultipleFiles) { } virtual void page_did_request_select_dropdown([[maybe_unused]] Web::CSSPixelPoint content_position, [[maybe_unused]] Web::CSSPixels minimum_width, [[maybe_unused]] Vector items) { } - virtual void page_did_finish_text_test([[maybe_unused]] String const& text) { } + virtual void page_did_finish_test([[maybe_unused]] String const& text) { } virtual void page_did_set_test_timeout([[maybe_unused]] double milliseconds) { } virtual void page_did_set_browser_zoom([[maybe_unused]] double factor) { } diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index c282e4806cf..0fcfbebd140 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -235,7 +235,7 @@ public: Function items)> on_request_select_dropdown; Function on_finish_handling_key_event; Function on_finish_handling_drag_event; - Function on_text_test_finish; + Function on_test_finish; Function on_set_test_timeout; Function on_set_browser_zoom; Function const& total_match_count)> on_find_in_page; diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index d1fc80efa75..6d742706f48 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -103,11 +103,11 @@ void WebContentClient::did_finish_loading(u64 page_id, URL::URL url) } } -void WebContentClient::did_finish_text_test(u64 page_id, String text) +void WebContentClient::did_finish_test(u64 page_id, String text) { if (auto view = view_for_page_id(page_id); view.has_value()) { - if (view->on_text_test_finish) - view->on_text_test_finish(text); + if (view->on_test_finish) + view->on_test_finish(text); } } diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index 8cc8cfef325..b2dde34bf44 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -119,7 +119,7 @@ private: virtual void did_request_file_picker(u64 page_id, Web::HTML::FileFilter accepted_file_types, Web::HTML::AllowMultipleFiles) override; virtual void did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector items) override; virtual void did_finish_handling_input_event(u64 page_id, Web::EventResult event_result) override; - virtual void did_finish_text_test(u64 page_id, String text) override; + virtual void did_finish_test(u64 page_id, String text) override; virtual void did_set_test_timeout(u64 page_id, double milliseconds) override; virtual void did_set_browser_zoom(u64 page_id, double factor) override; virtual void did_find_in_page(u64 page_id, size_t current_match_index, Optional total_match_count) override; diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index 60b095c71bb..ed3f59b7bfd 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -386,9 +386,9 @@ void PageClient::page_did_finish_loading(URL::URL const& url) client().async_did_finish_loading(m_id, url); } -void PageClient::page_did_finish_text_test(String const& text) +void PageClient::page_did_finish_test(String const& text) { - client().async_did_finish_text_test(m_id, text); + client().async_did_finish_test(m_id, text); } void PageClient::page_did_set_test_timeout(double milliseconds) diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index 88a49d40b2c..666feaa300c 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -165,7 +165,7 @@ private: virtual void page_did_request_color_picker(Color current_color) override; virtual void page_did_request_file_picker(Web::HTML::FileFilter const& accepted_file_types, Web::HTML::AllowMultipleFiles) override; virtual void page_did_request_select_dropdown(Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector items) override; - virtual void page_did_finish_text_test(String const& text) override; + virtual void page_did_finish_test(String const& text) override; virtual void page_did_set_test_timeout(double milliseconds) override; virtual void page_did_set_browser_zoom(double factor) override; virtual void page_did_change_theme_color(Gfx::Color color) override; diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index fdbe8317a9c..732b3839d4f 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -100,7 +100,7 @@ endpoint WebContentClient did_get_styled_js_console_messages(u64 page_id, i32 start_index, Vector message_types, Vector messages) =| did_get_unstyled_js_console_messages(u64 page_id, i32 start_index, Vector console_output) =| - did_finish_text_test(u64 page_id, String text) =| + did_finish_test(u64 page_id, String text) =| did_set_test_timeout(u64 page_id, double milliseconds) =| did_set_browser_zoom(u64 page_id, double factor) =| diff --git a/Tests/LibWeb/Text/input/HTML/document-write-flush-character-insertions.html b/Tests/LibWeb/Text/input/HTML/document-write-flush-character-insertions.html index 83241a7a2a2..974279af3c8 100644 --- a/Tests/LibWeb/Text/input/HTML/document-write-flush-character-insertions.html +++ b/Tests/LibWeb/Text/input/HTML/document-write-flush-character-insertions.html @@ -2,5 +2,5 @@ diff --git a/Tests/LibWeb/Text/input/IntersectionObserver/img-lazy-loading-not-blocked-by-img-outside-viewport.html b/Tests/LibWeb/Text/input/IntersectionObserver/img-lazy-loading-not-blocked-by-img-outside-viewport.html index 1dd23a541c6..7bbfd17f0f0 100644 --- a/Tests/LibWeb/Text/input/IntersectionObserver/img-lazy-loading-not-blocked-by-img-outside-viewport.html +++ b/Tests/LibWeb/Text/input/IntersectionObserver/img-lazy-loading-not-blocked-by-img-outside-viewport.html @@ -14,6 +14,6 @@ img { const inViewport = document.getElementById("inViewport"); inViewport.addEventListener("load", function() { document.body.innerHTML = "PASS"; - internals.signalTextTestIsDone(document.body.innerText); + internals.signalTestIsDone(document.body.innerText); }); diff --git a/Tests/LibWeb/Text/input/hit_testing/hit-testing-an-xml-svg-should-not-crash.svg b/Tests/LibWeb/Text/input/hit_testing/hit-testing-an-xml-svg-should-not-crash.svg index 48fdc0527d3..0cebed15f17 100644 --- a/Tests/LibWeb/Text/input/hit_testing/hit-testing-an-xml-svg-should-not-crash.svg +++ b/Tests/LibWeb/Text/input/hit_testing/hit-testing-an-xml-svg-should-not-crash.svg @@ -8,7 +8,7 @@ // Note: This test _must_ be in a .svg file! internals.hitTest(400, 400); // Did not crash! - internals.signalTextTestIsDone(""); + internals.signalTestIsDone(""); }); diff --git a/Tests/LibWeb/Text/input/include.js b/Tests/LibWeb/Text/input/include.js index d50a932ed67..7c81f80506a 100644 --- a/Tests/LibWeb/Text/input/include.js +++ b/Tests/LibWeb/Text/input/include.js @@ -10,7 +10,7 @@ function __preventMultipleTestFunctions() { if (globalThis.internals === undefined) { internals = { - signalTextTestIsDone: function () {}, + signalTestIsDone: function () {}, spoofCurrentURL: function (url) {}, }; } @@ -19,7 +19,7 @@ function __finishTest() { if (__originalURL) { internals.spoofCurrentURL(__originalURL); } - internals.signalTextTestIsDone(__outputElement.innerText); + internals.signalTestIsDone(__outputElement.innerText); } function spoofCurrentURL(url) { diff --git a/Tests/LibWeb/Text/input/module-script-in-head.html b/Tests/LibWeb/Text/input/module-script-in-head.html index ccffb8558dd..ca4e17ba917 100644 --- a/Tests/LibWeb/Text/input/module-script-in-head.html +++ b/Tests/LibWeb/Text/input/module-script-in-head.html @@ -5,6 +5,6 @@ __outputElement.setAttribute("id", "out"); __outputElement.appendChild(document.createTextNode("passed\n")) document.body.appendChild(__outputElement); - internals.signalTextTestIsDone(document.body.innerText); + internals.signalTestIsDone(document.body.innerText); diff --git a/Tests/LibWeb/Text/input/wpt-import/css/selectors/has-sibling-chrome-crash.html b/Tests/LibWeb/Text/input/wpt-import/css/selectors/has-sibling-chrome-crash.html index c698b26f266..1ea6481a3a1 100644 --- a/Tests/LibWeb/Text/input/wpt-import/css/selectors/has-sibling-chrome-crash.html +++ b/Tests/LibWeb/Text/input/wpt-import/css/selectors/has-sibling-chrome-crash.html @@ -10,5 +10,5 @@

PASS if this tests does not crash

diff --git a/Tests/LibWeb/Text/input/wpt-import/css/selectors/invalidation/nth-of-namespace-class-invalidation-crash.html b/Tests/LibWeb/Text/input/wpt-import/css/selectors/invalidation/nth-of-namespace-class-invalidation-crash.html index 02381260bb2..36ebe907bcf 100644 --- a/Tests/LibWeb/Text/input/wpt-import/css/selectors/invalidation/nth-of-namespace-class-invalidation-crash.html +++ b/Tests/LibWeb/Text/input/wpt-import/css/selectors/invalidation/nth-of-namespace-class-invalidation-crash.html @@ -12,6 +12,6 @@ document.addEventListener("DOMContentLoaded", () => { b.offsetTop; b.setAttributeNS("h", "class", "") if (window.internals) - window.internals.signalTextTestIsDone("PASS"); + window.internals.signalTestIsDone("PASS"); }) diff --git a/Tests/LibWeb/Text/input/wpt-import/html/dom/elements/the-innertext-and-outertext-properties/innertext-domnoderemoved-crash.html b/Tests/LibWeb/Text/input/wpt-import/html/dom/elements/the-innertext-and-outertext-properties/innertext-domnoderemoved-crash.html index 0aef8580227..fea0cb6a4e6 100644 --- a/Tests/LibWeb/Text/input/wpt-import/html/dom/elements/the-innertext-and-outertext-properties/innertext-domnoderemoved-crash.html +++ b/Tests/LibWeb/Text/input/wpt-import/html/dom/elements/the-innertext-and-outertext-properties/innertext-domnoderemoved-crash.html @@ -15,5 +15,5 @@ parentelement.innerText = 'hello world'; if (window.internals) - internals.signalTextTestIsDone("PASS"); + internals.signalTestIsDone("PASS"); diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testharnessreport.js b/Tests/LibWeb/Text/input/wpt-import/resources/testharnessreport.js index 7ab6ea74d65..da5309ba8fb 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testharnessreport.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testharnessreport.js @@ -53,7 +53,7 @@ add_completion_callback(function(tests, harness_status) { for (const test of tests) { outputLines.push(`${test.format_status()}\t${test.name}`); } - window.internals.signalTextTestIsDone(outputLines.join('\n')); + window.internals.signalTestIsDone(outputLines.join('\n')); } }); diff --git a/UI/Headless/Test.cpp b/UI/Headless/Test.cpp index 0b43bde7585..347f2fdc0f2 100644 --- a/UI/Headless/Test.cpp +++ b/UI/Headless/Test.cpp @@ -130,7 +130,7 @@ static ErrorOr collect_crash_tests(Application const& app, Vector& t static void clear_test_callbacks(HeadlessWebView& view) { view.on_load_finish = {}; - view.on_text_test_finish = {}; + view.on_test_finish = {}; view.on_web_content_crashed = {}; } @@ -138,7 +138,7 @@ void run_dump_test(HeadlessWebView& view, Test& test, URL::URL const& url, int t { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { view.on_load_finish = {}; - view.on_text_test_finish = {}; + view.on_test_finish = {}; view.on_set_test_timeout = {}; view.reset_zoom(); @@ -255,7 +255,7 @@ void run_dump_test(HeadlessWebView& view, Test& test, URL::URL const& url, int t } }; - view.on_text_test_finish = [&test, on_test_complete](auto const& text) { + view.on_test_finish = [&test, on_test_complete](auto const& text) { test.text = text; test.did_finish_test = true; @@ -290,7 +290,7 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { view.on_load_finish = {}; - view.on_text_test_finish = {}; + view.on_test_finish = {}; view.on_set_test_timeout = {}; view.reset_zoom(); @@ -364,7 +364,7 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, } }; - view.on_text_test_finish = [&](auto const&) { + view.on_test_finish = [&](auto const&) { dbgln("Unexpected text test finished during ref test for {}", url); }; @@ -398,7 +398,7 @@ static void run_test(HeadlessWebView& view, Test& test, Application& app) }); }; - view.on_text_test_finish = {}; + view.on_test_finish = {}; promise->when_resolved([&view, &test, &app](auto) { auto url = URL::create_with_file_scheme(MUST(FileSystem::real_path(test.input_path))); From 259d39cbadd342fe22451a616cf06ef6a62498e7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 18 Mar 2025 17:40:05 +0100 Subject: [PATCH 029/141] UI/Headless: Wait for "reftest-wait" class removal before screenshotting Resolves https://github.com/LadybirdBrowser/ladybird/issues/3984 --- UI/Headless/Test.cpp | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/UI/Headless/Test.cpp b/UI/Headless/Test.cpp index 347f2fdc0f2..e66d3176db6 100644 --- a/UI/Headless/Test.cpp +++ b/UI/Headless/Test.cpp @@ -286,6 +286,27 @@ void run_dump_test(HeadlessWebView& view, Test& test, URL::URL const& url, int t timer->start(); } +static String wait_for_reftest_completion = R"( +function hasReftestWaitClass() { + return document.documentElement.classList.contains('reftest-wait'); +} + +if (!hasReftestWaitClass()) { + internals.signalTestIsDone("PASS"); +} else { + const observer = new MutationObserver(() => { + if (!hasReftestWaitClass()) { + internals.signalTestIsDone("PASS"); + } + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'], + }); +} +)"_string; + static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, int timeout_in_milliseconds) { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { @@ -343,7 +364,11 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, view.on_test_complete({ test, TestResult::Crashed }); }; - view.on_load_finish = [&view, &test, on_test_complete = move(on_test_complete)](auto const&) { + view.on_load_finish = [&view](auto const&) { + view.run_javascript(wait_for_reftest_completion); + }; + + view.on_test_finish = [&view, &test, on_test_complete = move(on_test_complete)](auto const&) { if (test.actual_screenshot) { if (view.url().query().has_value() && view.url().query()->equals_ignoring_ascii_case("mismatch"sv)) { test.ref_test_expectation_type = RefTestExpectationType::Mismatch; @@ -364,10 +389,6 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, } }; - view.on_test_finish = [&](auto const&) { - dbgln("Unexpected text test finished during ref test for {}", url); - }; - view.on_set_test_timeout = [timer, timeout_in_milliseconds](double milliseconds) { if (milliseconds <= timeout_in_milliseconds) return; From 31da70bbfc99835eaf151c2b479d6b94456caf76 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 18 Mar 2025 17:41:48 +0100 Subject: [PATCH 030/141] Tests/LibWeb: Enable some tests relying on "reftest-wait" --- Tests/LibWeb/TestConfig.ini | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini index c95211fe355..833ee76dbd3 100644 --- a/Tests/LibWeb/TestConfig.ini +++ b/Tests/LibWeb/TestConfig.ini @@ -157,15 +157,6 @@ Text/input/wpt-import/css/css-backgrounds/animations/discrete-no-interpolation.h ; https://github.com/LadybirdBrowser/ladybird/issues/2900 Text/input/ShadowDOM/css-hover-shadow-dom.html -; WPT ref tests that are flaky, probably due to not supporting class="reftest-wait" -; https://github.com/LadybirdBrowser/ladybird/issues/3984 -Ref/input/wpt-import/css/css-contain/contain-layout-020.html -Ref/input/wpt-import/css/css-contain/contain-paint-050.html -Ref/input/wpt-import/css/css-contain/contain-paint-change-opacity.html -Ref/input/wpt-import/css/css-lists/list-style-type-string-004.html -Ref/input/wpt-import/css/css-transforms/individual-transform/stacking-context-001.html -Ref/input/wpt-import/png/apng/fDAT-inherits-cICP.html - ; Test is flaky on CI, as navigationStart time is not set according to spec. Text/input/wpt-import/user-timing/measure_associated_with_navigation_timing.html @@ -313,3 +304,6 @@ Text/input/wpt-import/html/syntax/parsing/html5lib_webkit02-write_single.html ; Inconsistently crashes because we haven't figured invalidation for CSS containment Crash/wpt-import/css/css-contain/contain-style-remove-element-crash.html +Ref/input/wpt-import/css/css-contain/contain-layout-020.html +Ref/input/wpt-import/css/css-contain/contain-paint-050.html +Ref/input/wpt-import/css/css-contain/contain-paint-change-opacity.html From 4dfc29356d29aaad3e3a6f34a0e89c94890ae5a2 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 18 Mar 2025 13:22:31 -0400 Subject: [PATCH 031/141] LibWeb: Initialize the Storage byte count upon creation A Storage object may be created with an existing storage bottle. For example, if you navigate from site.com/page1 to site.com/page2, they will have different localStorage objects, but will use the same bottle for actual storage. Previously, if page1 set some key/value item, we would initialize the byte count to 0 on page2 despite having a non-empty bottle. Thus, if page2 set a smaller value with the same key, we would overflow the computed byte count, and all subsequent writes would be rejected. This was seen navigating from the chess.com home page to the daily puzzle page. --- Libraries/LibWeb/HTML/Storage.cpp | 3 +++ .../LibWeb/Text/data/local-storage-iframe.html | 4 ++++ .../HTML/local-storage-usage-after-nav.txt | 2 ++ .../HTML/local-storage-usage-after-nav.html | 17 +++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 Tests/LibWeb/Text/data/local-storage-iframe.html create mode 100644 Tests/LibWeb/Text/expected/HTML/local-storage-usage-after-nav.txt create mode 100644 Tests/LibWeb/Text/input/HTML/local-storage-usage-after-nav.html diff --git a/Libraries/LibWeb/HTML/Storage.cpp b/Libraries/LibWeb/HTML/Storage.cpp index 6eb9e63352b..512388df972 100644 --- a/Libraries/LibWeb/HTML/Storage.cpp +++ b/Libraries/LibWeb/HTML/Storage.cpp @@ -46,6 +46,9 @@ Storage::Storage(JS::Realm& realm, Type type, NonnullRefPtr + localStorage.setItem("foo", "barbaz"); + parent.postMessage("local storage set", "*"); + diff --git a/Tests/LibWeb/Text/expected/HTML/local-storage-usage-after-nav.txt b/Tests/LibWeb/Text/expected/HTML/local-storage-usage-after-nav.txt new file mode 100644 index 00000000000..8101bdaccf6 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/local-storage-usage-after-nav.txt @@ -0,0 +1,2 @@ +barbaz +bar diff --git a/Tests/LibWeb/Text/input/HTML/local-storage-usage-after-nav.html b/Tests/LibWeb/Text/input/HTML/local-storage-usage-after-nav.html new file mode 100644 index 00000000000..5038ee36485 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/local-storage-usage-after-nav.html @@ -0,0 +1,17 @@ + + + From 97e917bdf5bc8b4785ab4781f8652654d5d4f58c Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 14:34:23 +0000 Subject: [PATCH 032/141] LibWeb/CSS: Allow bare zero for gradient angles Corresponds to https://github.com/w3c/csswg-drafts/commit/f952e97da9fa75a3d283c61febc8173e3926f48e --- .../LibWeb/CSS/Parser/GradientParsing.cpp | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp b/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp index d44a525a974..3b0090abf1e 100644 --- a/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp @@ -251,7 +251,7 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr if (!function_name.equals_ignoring_ascii_case("linear-gradient"sv)) return nullptr; - // = [ [ | to ] || ]? , + // = [ [ | | to ] || ]? , TokenStream tokens { component_value.function().value }; tokens.discard_whitespace(); @@ -290,7 +290,7 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr auto const& first_param = tokens.next_token(); if (first_param.is(Token::Type::Dimension)) { // - tokens.discard_a_token(); + tokens.discard_a_token(); // auto angle_value = first_param.token().dimension_value(); auto unit_string = first_param.token().dimension_unit(); auto angle_type = Angle::unit_from_name(unit_string); @@ -299,6 +299,10 @@ RefPtr Parser::parse_linear_gradient_function(TokenStr return nullptr; gradient_direction = Angle { angle_value, angle_type.release_value() }; + } else if (first_param.is(Token::Type::Number) && first_param.token().number().value() == 0) { + // + tokens.discard_a_token(); // + gradient_direction = Angle::make_degrees(0); } else if (is_to_side_or_corner(first_param)) { // = [left | right] || [top | bottom] @@ -396,7 +400,7 @@ RefPtr Parser::parse_conic_gradient_function(TokenStrea RefPtr at_position; Optional maybe_interpolation_method; - // conic-gradient( [ [ [ from ]? [ at ]? ] || ]? , ) + // conic-gradient( [ [ [ from [ | ] ]? [ at ]? ] || ]? , ) NonnullRawPtr token = tokens.next_token(); bool got_from_angle = false; bool got_color_interpolation_method = false; @@ -413,23 +417,28 @@ RefPtr Parser::parse_conic_gradient_function(TokenStrea }; if (consume_identifier("from"sv)) { - // from + // from [ | ] if (got_from_angle || got_at_position) return nullptr; if (!tokens.has_next_token()) return nullptr; auto const& angle_token = tokens.consume_a_token(); - if (!angle_token.is(Token::Type::Dimension)) - return nullptr; - auto angle = angle_token.token().dimension_value(); - auto angle_unit = angle_token.token().dimension_unit(); - auto angle_type = Angle::unit_from_name(angle_unit); - if (!angle_type.has_value()) - return nullptr; + if (angle_token.is(Token::Type::Dimension)) { + auto angle = angle_token.token().dimension_value(); + auto angle_unit = angle_token.token().dimension_unit(); + auto angle_type = Angle::unit_from_name(angle_unit); + if (!angle_type.has_value()) + return nullptr; - from_angle = Angle(angle, *angle_type); - got_from_angle = true; + from_angle = Angle(angle, *angle_type); + got_from_angle = true; + } else if (angle_token.is(Token::Type::Number) && angle_token.token().number().value() == 0) { + from_angle = Angle::make_degrees(0); + got_from_angle = true; + } else { + return nullptr; + } } else if (consume_identifier("at"sv)) { // at if (got_at_position) From 040dca022323ed882982a44ae6378131839aff25 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 18 Mar 2025 12:46:51 +0000 Subject: [PATCH 033/141] AK: Add `first_is_equal_to_all_of()` This method returns true if all arguments are equal. --- AK/GenericShorthands.h | 7 +++++++ Tests/AK/TestGenericShorthands.cpp | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/AK/GenericShorthands.h b/AK/GenericShorthands.h index 57f7f8a32f4..06fe1bba743 100644 --- a/AK/GenericShorthands.h +++ b/AK/GenericShorthands.h @@ -41,6 +41,12 @@ template return (... || (forward(to_compare) >= forward(valid_values))); } +template +[[nodiscard]] constexpr bool first_is_equal_to_all_of(T&& to_compare, Ts&&... valid_values) +{ + return (... && (forward(to_compare) == forward(valid_values))); +} + template [[nodiscard]] constexpr bool first_is_smaller_than_all_of(T&& to_compare, Ts&&... valid_values) { @@ -67,6 +73,7 @@ template } #if USING_AK_GLOBALLY +using AK::first_is_equal_to_all_of; using AK::first_is_larger_or_equal_than_all_of; using AK::first_is_larger_or_equal_than_one_of; using AK::first_is_larger_than_all_of; diff --git a/Tests/AK/TestGenericShorthands.cpp b/Tests/AK/TestGenericShorthands.cpp index 41af2da3104..6529281032b 100644 --- a/Tests/AK/TestGenericShorthands.cpp +++ b/Tests/AK/TestGenericShorthands.cpp @@ -203,6 +203,27 @@ TEST_CASE(first_is_larger_than_one_of) EXPECT(!first_is_larger_than_one_of(10)); } +TEST_CASE(first_is_equal_to_all_of) +{ + static_assert(first_is_equal_to_all_of(1)); + EXPECT(first_is_equal_to_all_of(1)); + + static_assert(first_is_equal_to_all_of(1, 1)); + EXPECT(first_is_equal_to_all_of(1, 1)); + + static_assert(!first_is_equal_to_all_of(1, 2)); + EXPECT(!first_is_equal_to_all_of(1, 2)); + + static_assert(!first_is_equal_to_all_of(1, 1, 2)); + EXPECT(!first_is_equal_to_all_of(1, 1, 2)); + + static_assert(!first_is_equal_to_all_of(2, 1, 1)); + EXPECT(!first_is_equal_to_all_of(2, 1, 1)); + + static_assert(!first_is_equal_to_all_of(2, 2, 1)); + EXPECT(!first_is_equal_to_all_of(2, 2, 1)); +} + TEST_CASE(first_is_larger_or_equal_than_all_of) { // Finds larger than all items From 85728b297f5fd92aed1443526722a406b88ecbae Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 18 Mar 2025 12:49:16 +0000 Subject: [PATCH 034/141] LibWeb: Ensure the shortest serialization is used for `border-radius` This implementation also fixes an issue where the individual components of the `border-radius` shorthand were always assumed to be of type `BorderRadiusStyleValue`, which could lead to a crash when CSS-wide keywords were used. --- .../CSS/StyleValues/ShorthandStyleValue.cpp | 56 ++++++++++++++----- ...upported-properties-and-default-values.txt | 10 ++-- .../parsing/border-radius-valid.txt | 29 ++++++++++ .../parsing/border-radius-valid.html | 42 ++++++++++++++ 4 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-radius-valid.html diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index e4efcb47ce8..90363c03366 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -134,20 +134,50 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const return MUST(builder.to_string()); } case PropertyID::BorderRadius: { - auto& top_left = longhand(PropertyID::BorderTopLeftRadius)->as_border_radius(); - auto& top_right = longhand(PropertyID::BorderTopRightRadius)->as_border_radius(); - auto& bottom_right = longhand(PropertyID::BorderBottomRightRadius)->as_border_radius(); - auto& bottom_left = longhand(PropertyID::BorderBottomLeftRadius)->as_border_radius(); + auto top_left = longhand(PropertyID::BorderTopLeftRadius); + auto top_right = longhand(PropertyID::BorderTopRightRadius); + auto bottom_right = longhand(PropertyID::BorderBottomRightRadius); + auto bottom_left = longhand(PropertyID::BorderBottomLeftRadius); - return MUST(String::formatted("{} {} {} {} / {} {} {} {}", - top_left.horizontal_radius().to_string(), - top_right.horizontal_radius().to_string(), - bottom_right.horizontal_radius().to_string(), - bottom_left.horizontal_radius().to_string(), - top_left.vertical_radius().to_string(), - top_right.vertical_radius().to_string(), - bottom_right.vertical_radius().to_string(), - bottom_left.vertical_radius().to_string())); + auto horizontal_radius = [&](auto& style_value) -> String { + if (style_value->is_border_radius()) + return style_value->as_border_radius().horizontal_radius().to_string(); + return style_value->to_string(mode); + }; + + auto top_left_horizontal_string = horizontal_radius(top_left); + auto top_right_horizontal_string = horizontal_radius(top_right); + auto bottom_right_horizontal_string = horizontal_radius(bottom_right); + auto bottom_left_horizontal_string = horizontal_radius(bottom_left); + + auto vertical_radius = [&](auto& style_value) -> String { + if (style_value->is_border_radius()) + return style_value->as_border_radius().vertical_radius().to_string(); + return style_value->to_string(mode); + }; + + auto top_left_vertical_string = vertical_radius(top_left); + auto top_right_vertical_string = vertical_radius(top_right); + auto bottom_right_vertical_string = vertical_radius(bottom_right); + auto bottom_left_vertical_string = vertical_radius(bottom_left); + + auto serialize_radius = [](auto top_left, auto const& top_right, auto const& bottom_right, auto const& bottom_left) -> String { + if (first_is_equal_to_all_of(top_left, top_right, bottom_right, bottom_left)) + return top_left; + if (top_left == bottom_right && top_right == bottom_left) + return MUST(String::formatted("{} {}", top_left, top_right)); + if (top_right == bottom_left) + return MUST(String::formatted("{} {} {}", top_left, top_right, bottom_right)); + + return MUST(String::formatted("{} {} {} {}", top_left, top_right, bottom_right, bottom_left)); + }; + + auto first_radius_serialization = serialize_radius(move(top_left_horizontal_string), top_right_horizontal_string, bottom_right_horizontal_string, bottom_left_horizontal_string); + auto second_radius_serialization = serialize_radius(move(top_left_vertical_string), top_right_vertical_string, bottom_right_vertical_string, bottom_left_vertical_string); + if (first_radius_serialization == second_radius_serialization) + return first_radius_serialization; + + return MUST(String::formatted("{} / {}", first_radius_serialization, second_radius_serialization)); } case PropertyID::Columns: { auto column_width = longhand(PropertyID::ColumnWidth)->to_string(mode); diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt index 7d06a3f642d..aea43775afe 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt @@ -57,9 +57,9 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'WebkitBorderBottomRightRadius': '0px' 'webkitBorderBottomRightRadius': '0px' '-webkit-border-bottom-right-radius': '0px' -'WebkitBorderRadius': '0px 0px 0px 0px / 0px 0px 0px 0px' -'webkitBorderRadius': '0px 0px 0px 0px / 0px 0px 0px 0px' -'-webkit-border-radius': '0px 0px 0px 0px / 0px 0px 0px 0px' +'WebkitBorderRadius': '0px' +'webkitBorderRadius': '0px' +'-webkit-border-radius': '0px' 'WebkitBorderTopLeftRadius': '0px' 'webkitBorderTopLeftRadius': '0px' '-webkit-border-top-left-radius': '0px' @@ -242,8 +242,8 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'border-left-style': 'none' 'borderLeftWidth': 'medium' 'border-left-width': 'medium' -'borderRadius': '0px 0px 0px 0px / 0px 0px 0px 0px' -'border-radius': '0px 0px 0px 0px / 0px 0px 0px 0px' +'borderRadius': '0px' +'border-radius': '0px' 'borderRight': 'medium none rgb(0, 0, 0)' 'border-right': 'medium none rgb(0, 0, 0)' 'borderRightColor': 'rgb(0, 0, 0)' diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt new file mode 100644 index 00000000000..32491ba978b --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt @@ -0,0 +1,29 @@ +Harness status: OK + +Found 23 tests + +21 Pass +2 Fail +Pass e.style['border-radius'] = "initial" should set the property value +Pass e.style['border-radius'] = "inherit" should set the property value +Pass e.style['border-radius'] = "unset" should set the property value +Pass e.style['border-radius'] = "revert" should set the property value +Pass e.style['border-radius'] = "1px" should set the property value +Pass e.style['border-radius'] = "1px 5%" should set the property value +Pass e.style['border-radius'] = "1px 2% 3px" should set the property value +Pass e.style['border-radius'] = "1px 2% 3px 4%" should set the property value +Pass e.style['border-radius'] = "1px / 2px" should set the property value +Pass e.style['border-radius'] = "5em / 1px 2% 3px 4%" should set the property value +Pass e.style['border-radius'] = "1px 2% / 3px 4px" should set the property value +Pass e.style['border-radius'] = "1px 2px 3em / 1px 2px 3%" should set the property value +Pass e.style['border-radius'] = "1px 2% / 2px 3em 4px 5em" should set the property value +Pass e.style['border-radius'] = "1px 2% 3px 4% / 5em" should set the property value +Pass e.style['border-radius'] = "1px 1px 1px 2% / 1px 2% 1px 2%" should set the property value +Pass e.style['border-radius'] = "1px 1px 1px 1px / 1px 1px 2% 1px" should set the property value +Pass e.style['border-radius'] = "1px 1px 2% 2%" should set the property value +Pass e.style['border-radius'] = "1px 2% 1px 1px" should set the property value +Pass e.style['border-radius'] = "1px 2% 2% 2% / 1px 2% 3px 2%" should set the property value +Pass e.style['border-top-left-radius'] = "10px" should set the property value +Pass e.style['border-top-right-radius'] = "20%" should set the property value +Fail e.style['border-bottom-right-radius'] = "30px 40%" should set the property value +Fail e.style['border-bottom-left-radius'] = "50% 60px" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-radius-valid.html b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-radius-valid.html new file mode 100644 index 00000000000..f2d5ffae59d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-radius-valid.html @@ -0,0 +1,42 @@ + + + + +CSS Backgrounds and Borders Module Level 3: parsing border-radius with valid values + + + + + + + + + + From f6a8e5aa6856a5a13b2a67eb9b331cbdff235046 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 18 Mar 2025 12:52:58 +0000 Subject: [PATCH 035/141] LibWeb: Use correct canonical serialization for `BorderRadiusStyleValue` --- .../LibWeb/CSS/StyleValues/BorderRadiusStyleValue.cpp | 2 +- .../css/css-backgrounds/parsing/border-radius-valid.txt | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.cpp index 0cba0161268..858983e778a 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.cpp @@ -15,7 +15,7 @@ String BorderRadiusStyleValue::to_string(SerializationMode) const { if (m_properties.horizontal_radius == m_properties.vertical_radius) return m_properties.horizontal_radius.to_string(); - return MUST(String::formatted("{} / {}", m_properties.horizontal_radius.to_string(), m_properties.vertical_radius.to_string())); + return MUST(String::formatted("{} {}", m_properties.horizontal_radius.to_string(), m_properties.vertical_radius.to_string())); } ValueComparingNonnullRefPtr BorderRadiusStyleValue::absolutized(CSSPixelRect const& viewport_rect, Length::FontMetrics const& font_metrics, Length::FontMetrics const& root_font_metrics) const diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt index 32491ba978b..70537af92e7 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-radius-valid.txt @@ -2,8 +2,7 @@ Harness status: OK Found 23 tests -21 Pass -2 Fail +23 Pass Pass e.style['border-radius'] = "initial" should set the property value Pass e.style['border-radius'] = "inherit" should set the property value Pass e.style['border-radius'] = "unset" should set the property value @@ -25,5 +24,5 @@ Pass e.style['border-radius'] = "1px 2% 1px 1px" should set the property value Pass e.style['border-radius'] = "1px 2% 2% 2% / 1px 2% 3px 2%" should set the property value Pass e.style['border-top-left-radius'] = "10px" should set the property value Pass e.style['border-top-right-radius'] = "20%" should set the property value -Fail e.style['border-bottom-right-radius'] = "30px 40%" should set the property value -Fail e.style['border-bottom-left-radius'] = "50% 60px" should set the property value \ No newline at end of file +Pass e.style['border-bottom-right-radius'] = "30px 40%" should set the property value +Pass e.style['border-bottom-left-radius'] = "50% 60px" should set the property value \ No newline at end of file From 1148116a870e5f995d9cc32362c32437a1c9085d Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sat, 15 Mar 2025 21:53:30 -0600 Subject: [PATCH 036/141] CMake: Allow passing test name to serenity_test This forwards to lagom_test. One day we should simplify this.. --- Meta/Lagom/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 11bdb1b4b0c..1335b26b6a1 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -281,9 +281,9 @@ function(lagom_utility name) endfunction() function(serenity_test test_src sub_dir) - cmake_parse_arguments(PARSE_ARGV 2 SERENITY_TEST "MAIN_ALREADY_DEFINED" "CUSTOM_MAIN" "LIBS") + cmake_parse_arguments(PARSE_ARGV 2 SERENITY_TEST "MAIN_ALREADY_DEFINED" "CUSTOM_MAIN;NAME" "LIBS") # FIXME: Pass MAIN_ALREADY_DEFINED and CUSTOM_MAIN to support tests that use them. - lagom_test(${test_src} LIBS ${SERENITY_TEST_LIBS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + lagom_test(${test_src} LIBS ${SERENITY_TEST_LIBS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} NAME ${SERENITY_TEST_NAME}) endfunction() function(serenity_bin name) From 0c2f434e690c7ff05f6a3bd14001be944a293985 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sun, 16 Mar 2025 17:07:53 -0600 Subject: [PATCH 037/141] AK: Add feature detection for -fblocks and -fobjc-arc --- AK/Platform.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AK/Platform.h b/AK/Platform.h index 807f6f7038e..ad719cbf160 100644 --- a/AK/Platform.h +++ b/AK/Platform.h @@ -265,6 +265,14 @@ # define LSAN_UNREGISTER_ROOT_REGION(base, size) #endif +#if __has_feature(blocks) +# define AK_HAS_BLOCKS +#endif + +#if __has_feature(objc_arc) +# define AK_HAS_OBJC_ARC +#endif + #ifndef AK_OS_SERENITY # ifdef AK_OS_WINDOWS // FIXME: No idea where to get this, but it's 4096 anyway :^) From be84ff4f2c721956254a2ff10ae86030c84df21d Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sun, 16 Mar 2025 17:08:40 -0600 Subject: [PATCH 038/141] AK: Add cast using objective-c __bridge qualifier --- AK/TypeCasts.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AK/TypeCasts.h b/AK/TypeCasts.h index bd55da03d1a..5c95fb3ff23 100644 --- a/AK/TypeCasts.h +++ b/AK/TypeCasts.h @@ -73,10 +73,21 @@ ALWAYS_INLINE CopyConst* as(InputType* input) return result; } +template +ALWAYS_INLINE CopyConst* bridge_cast(InputType input) +{ +#ifdef AK_HAS_OBJC_ARC + return (__bridge CopyConst*)(input); +#else + return static_cast*>(input); +#endif +} + } #if USING_AK_GLOBALLY using AK::as; using AK::as_if; +using AK::bridge_cast; using AK::is; #endif From 72acb1111f96ed541f27486f75c1299c8515bfd5 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 17 Mar 2025 17:45:18 -0600 Subject: [PATCH 039/141] CMake: Add find module for BlocksRuntime on non-Apple platforms --- Meta/CMake/FindBlocksRuntime.cmake | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Meta/CMake/FindBlocksRuntime.cmake diff --git a/Meta/CMake/FindBlocksRuntime.cmake b/Meta/CMake/FindBlocksRuntime.cmake new file mode 100644 index 00000000000..b8ed277c1d3 --- /dev/null +++ b/Meta/CMake/FindBlocksRuntime.cmake @@ -0,0 +1,19 @@ +# Finds the BlocksRuntime library +# On Apple platforms, this does not exist and is folded into other System libraries + +find_library(BLOCKS_RUNTIME NAMES BlocksRuntime + PATHS ${SWIFT_LIBRARY_SEARCH_PATHS} +) +if (BLOCKS_RUNTIME) + if (NOT TARGET BlocksRuntime::BlocksRuntime) + add_library(BlocksRuntime::BlocksRuntime IMPORTED UNKNOWN) + message(STATUS "Found BlocksRuntime: ${BLOCKS_RUNTIME}") + cmake_path(GET BLOCKS_RUNTIME PARENT_PATH _BLOCKS_RUNTIME_DIR) + set_target_properties(BlocksRuntime::BlocksRuntime PROPERTIES + IMPORTED_LOCATION "${BLOCKS_RUNTIME}" + INTERFACE_LINK_DIRECTORIES "${_BLOCKS_RUNTIME_DIR}" + INTERFACE_COMPILE_OPTIONS "$<$:-fblocks>;SHELL:$<$:-Xcc -fblocks>" + ) + endif() + set(BlocksRuntime_FOUND TRUE) +endif() From 01ac48b36f3fc67bbbc0296a901979eb0edee396 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sun, 16 Mar 2025 17:35:38 -0600 Subject: [PATCH 040/141] AK: Support storing blocks in AK::Function This has two slightly different implementations for ARC and non-ARC compiler modes. The main idea is to store a block pointer as our closure and use either ARC magic or BlockRuntime methods to manage the memory for the block. Things are complicated by the fact that we don't yet force-enable swift, so we can't count on the swift.org llvm fork being our compiler toolchain. The patch adds some CMake checks and ifdefs to still support environments without support for blocks or ARC. --- AK/Function.h | 102 +++++++++++++++++++--- AK/StdLibExtraDetails.h | 22 +++++ Meta/CMake/common_options.cmake | 19 +++++ Tests/AK/CMakeLists.txt | 10 +++ Tests/AK/TestFunction.mm | 145 ++++++++++++++++++++++++++++++++ 5 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 Tests/AK/TestFunction.mm diff --git a/AK/Function.h b/AK/Function.h index 3faa013ffab..182dc06e104 100644 --- a/AK/Function.h +++ b/AK/Function.h @@ -2,6 +2,7 @@ * Copyright (C) 2016 Apple Inc. All rights reserved. * Copyright (c) 2021, Gunnar Beutner * Copyright (c) 2018-2023, Andreas Kling + * Copyright (c) 2025, Andrew Kaster * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -34,8 +35,14 @@ #include #include #include +#include #include +// BlockRuntime methods for Objective-C block closure support. +extern "C" void* _Block_copy(void const*); +extern "C" void _Block_release(void const*); +extern "C" size_t Block_size(void const*); + namespace AK { // These annotations are used to avoid capturing a variable with local storage in a lambda that outlives it @@ -48,6 +55,17 @@ namespace AK { # define IGNORE_USE_IN_ESCAPING_LAMBDA #endif +namespace Detail { +#ifdef AK_HAS_OBJC_ARC +inline constexpr bool HaveObjcArc = true; +#else +inline constexpr bool HaveObjcArc = false; +#endif + +// validated in TestFunction.mm +inline constexpr size_t block_layout_size = 32; +} + template class Function; @@ -84,7 +102,7 @@ public: if (!m_size) return {}; if (auto* wrapper = callable_wrapper()) - return ReadonlyBytes { wrapper, m_size }; + return ReadonlyBytes { wrapper->raw_callable(), m_size }; return {}; } @@ -102,6 +120,13 @@ public: init_with_callable(move(f), CallableKind::FunctionPointer); } + template + Function(BlockType b) + requires((IsBlockClosure && IsCallableWithArguments)) + { + init_with_callable(move(b), CallableKind::Block); + } + Function(Function&& other) { move_from(move(other)); @@ -141,6 +166,15 @@ public: return *this; } + template + Function& operator=(BlockType&& block) + requires((IsBlockClosure && IsCallableWithArguments)) + { + clear(); + init_with_callable(static_cast>(block), CallableKind::Block); + return *this; + } + Function& operator=(nullptr_t) { clear(); @@ -160,6 +194,7 @@ private: enum class CallableKind { FunctionPointer, FunctionObject, + Block, }; class CallableWrapperBase { @@ -169,6 +204,7 @@ private: virtual Out call(In...) = 0; virtual void destroy() = 0; virtual void init_and_swap(u8*, size_t) = 0; + virtual void const* raw_callable() const = 0; }; template @@ -189,7 +225,15 @@ private: void destroy() final override { - delete this; + if constexpr (IsBlockClosure) { + if constexpr (Detail::HaveObjcArc) + m_callable = nullptr; + else + _Block_release(m_callable); + } else { + // This code is a bit too clever for gcc. Pinky promise we're only deleting heap objects. + AK_IGNORE_DIAGNOSTIC("-Wfree-nonheap-object", delete this); + } } // NOLINTNEXTLINE(readability-non-const-parameter) False positive; destination is used in a placement new expression @@ -199,6 +243,14 @@ private: new (destination) CallableWrapper { move(m_callable) }; } + void const* raw_callable() const final override + { + if constexpr (IsBlockClosure) + return static_cast(bridge_cast(m_callable)) + Detail::block_layout_size; + else + return &m_callable; + } + private: CallableType m_callable; }; @@ -207,6 +259,7 @@ private: NullPointer, Inline, Outline, + Block, }; CallableWrapperBase* callable_wrapper() const @@ -215,6 +268,7 @@ private: case FunctionKind::NullPointer: return nullptr; case FunctionKind::Inline: + case FunctionKind::Block: return bit_cast(&m_storage); case FunctionKind::Outline: return *bit_cast(&m_storage); @@ -234,12 +288,22 @@ private: } m_deferred_clear = false; auto* wrapper = callable_wrapper(); - if (m_kind == FunctionKind::Inline) { + switch (m_kind) { + case FunctionKind::Inline: VERIFY(wrapper); wrapper->~CallableWrapperBase(); - } else if (m_kind == FunctionKind::Outline) { + break; + case FunctionKind::Outline: VERIFY(wrapper); wrapper->destroy(); + break; + case FunctionKind::Block: + VERIFY(wrapper); + wrapper->destroy(); + wrapper->~CallableWrapperBase(); + break; + case FunctionKind::NullPointer: + break; } m_kind = FunctionKind::NullPointer; } @@ -256,18 +320,33 @@ private: } VERIFY(m_call_nesting_level == 0); using WrapperType = CallableWrapper; + if (callable_kind == CallableKind::FunctionObject) + m_size = sizeof(Callable); + else + m_size = 0; + if constexpr (IsBlockClosure) { + auto block_size = Block_size(bridge_cast(callable)); + VERIFY(block_size >= Detail::block_layout_size); + m_size = block_size - Detail::block_layout_size; + } + if constexpr (alignof(Callable) > inline_alignment || sizeof(WrapperType) > inline_capacity) { *bit_cast(&m_storage) = new WrapperType(forward(callable)); m_kind = FunctionKind::Outline; } else { static_assert(sizeof(WrapperType) <= inline_capacity); - new (m_storage) WrapperType(forward(callable)); - m_kind = FunctionKind::Inline; + if constexpr (IsBlockClosure) { + if constexpr (Detail::HaveObjcArc) { + new (m_storage) WrapperType(forward(callable)); + } else { + new (m_storage) WrapperType(reinterpret_cast(_Block_copy(callable))); + } + m_kind = FunctionKind::Block; + } else { + new (m_storage) WrapperType(forward(callable)); + m_kind = FunctionKind::Inline; + } } - if (callable_kind == CallableKind::FunctionObject) - m_size = sizeof(WrapperType); - else - m_size = 0; } void move_from(Function&& other) @@ -279,8 +358,9 @@ private: case FunctionKind::NullPointer: break; case FunctionKind::Inline: + case FunctionKind::Block: other_wrapper->init_and_swap(m_storage, inline_capacity); - m_kind = FunctionKind::Inline; + m_kind = other.m_kind; break; case FunctionKind::Outline: *bit_cast(&m_storage) = other_wrapper; diff --git a/AK/StdLibExtraDetails.h b/AK/StdLibExtraDetails.h index 15aded72993..d3b5be83bec 100644 --- a/AK/StdLibExtraDetails.h +++ b/AK/StdLibExtraDetails.h @@ -142,6 +142,27 @@ inline constexpr bool IsFunction = true; template inline constexpr bool IsFunction = true; +template +inline constexpr bool IsBlockClosure = false; +#ifdef AK_HAS_BLOCKS +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +template +inline constexpr bool IsBlockClosure = true; +#endif + template inline constexpr bool IsRvalueReference = false; template @@ -641,6 +662,7 @@ using AK::Detail::InvokeResult; using AK::Detail::IsArithmetic; using AK::Detail::IsAssignable; using AK::Detail::IsBaseOf; +using AK::Detail::IsBlockClosure; using AK::Detail::IsClass; using AK::Detail::IsConst; using AK::Detail::IsConstructible; diff --git a/Meta/CMake/common_options.cmake b/Meta/CMake/common_options.cmake index 23bd279c767..4cf6863b897 100644 --- a/Meta/CMake/common_options.cmake +++ b/Meta/CMake/common_options.cmake @@ -45,3 +45,22 @@ serenity_option(ENABLE_STD_STACKTRACE OFF CACHE BOOL "Force use of std::stacktra if (ENABLE_SWIFT) include(${CMAKE_CURRENT_LIST_DIR}/Swift/swift-settings.cmake) endif() + +include(CheckCXXSourceCompiles) +set(BLOCKS_REQUIRED_LIBRARIES "") +if (NOT APPLE) + find_package(BlocksRuntime) + if (BlocksRuntime_FOUND) + set(BLOCKS_REQUIRED_LIBRARIES BlocksRuntime::BlocksRuntime) + set(CMAKE_REQUIRED_LIBRARIES BlocksRuntime::BlocksRuntime) + endif() +endif() +check_cxx_source_compiles([=[ + int main() { __block int x = 0; auto b = ^{++x;}; b(); } +]=] CXX_COMPILER_SUPPORTS_BLOCKS) + +set(CMAKE_REQUIRED_FLAGS "-fobjc-arc") +check_cxx_source_compiles([=[ + int main() { auto b = ^{}; auto __weak w = b; w(); } +]=] CXX_COMPILER_SUPPORTS_OBJC_ARC) +unset(CMAKE_REQUIRED_FLAGS) diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index 10f4a245050..92923f9a67b 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -88,6 +88,16 @@ foreach(source IN LISTS AK_TEST_SOURCES) serenity_test("${source}" AK) endforeach() +if (CXX_COMPILER_SUPPORTS_BLOCKS) + serenity_test(TestFunction.mm AK NAME TestFunction) + target_link_libraries(TestFunction PRIVATE ${BLOCKS_REQUIRED_LIBRARIES}) +endif() +if (CXX_COMPILER_SUPPORTS_OBJC_ARC) + serenity_test(TestFunction.mm AK NAME TestFunctionArc) + target_compile_options(TestFunctionArc PRIVATE -fobjc-arc) + target_link_libraries(TestFunction PRIVATE ${BLOCKS_REQUIRED_LIBRARIES}) +endif() + target_link_libraries(TestString PRIVATE LibUnicode) if (ENABLE_SWIFT) diff --git a/Tests/AK/TestFunction.mm b/Tests/AK/TestFunction.mm new file mode 100644 index 00000000000..3d6f562c96c --- /dev/null +++ b/Tests/AK/TestFunction.mm @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include + +TEST_CASE(SimpleBlock) +{ + auto b = ^{ }; + + static_assert(IsBlockClosure); + + auto f = Function(b); + + f(); +} + +TEST_CASE(BlockCaptureInt) +{ + __block int x = 0; + auto b = ^{ + x = 2; + }; + auto f = Function(b); + + f(); + + EXPECT_EQ(x, 2); +} + +TEST_CASE(BlockCaptureString) +{ + __block String s = "hello"_string; + auto b = ^{ + s = "world"_string; + }; + auto f = Function(b); + + f(); + + EXPECT_EQ(s, "world"_string); +} + +TEST_CASE(BlockCaptureLongStringAndInt) +{ + __block String s = "hello, world, this is a long string to avoid small string optimization"_string; + __block int x = 0; + auto b = ^{ + s = "world, hello, this is a long string to avoid small string optimization"_string; + x = 2; + }; + auto f = Function(b); + + f(); + + EXPECT_EQ(s, "world, hello, this is a long string to avoid small string optimization"_string); + EXPECT_EQ(x, 2); +} + +// Struct definitions from llvm-project/compiler-rt/lib/BlocksRuntime/Block_private.h @ d0177670a0e59e9d9719386f85bb78de0929407c +struct Block_descriptor { + unsigned long int reserved; + unsigned long int size; + void (*copy)(void* dst, void* src); + void (*dispose)(void*); +}; + +struct Block_layout { + void* isa; + int flags; + int reserved; + void (*invoke)(void*, ...); + struct Block_descriptor* descriptor; + /* Imported variables. */ +}; +// This check is super important for proper tracking of block closure captures +static_assert(sizeof(Block_layout) == AK::Detail::block_layout_size); + +TEST_CASE(BlockPointerCaptures) +{ + int x = 0; + int* p = &x; + + auto b = ^{ + *p = 2; + }; + + auto f = Function(b); + auto span = f.raw_capture_range(); + + int* captured_p = ByteReader::load_pointer(span.data()); + EXPECT_EQ(captured_p, p); + + f(); + + EXPECT_EQ(x, 2); +} + +TEST_CASE(AssignBlock) +{ + auto b = ^{ }; + + auto f = Function(b); + + auto b2 = ^{ }; + + f = b2; + + f(); + + f = b; + + f(); +} + +#ifdef AK_HAS_OBJC_ARC +TEST_CASE(AssignWeakBlock) +{ + __block int count = 0; + Function f; + + { + auto b = ^{ ++count; }; + f = b; + } + f(); + EXPECT_EQ(count, 1); + + { + auto b = ^{ ++count; }; + auto const __weak weak_b = b; + f = weak_b; + f(); + EXPECT_EQ(count, 2); + } + f(); + EXPECT_EQ(count, 3); +} +#endif From 89ecc75ed8610e3219634790f7463c21ddcb13ad Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 17 Mar 2025 17:46:42 -0600 Subject: [PATCH 041/141] LibCore+Meta: Un-break Swift build on Linux LibCore's list of ignored header files for Swift was missing the Apple only files on non-Apple platforms. Additionally, any generic glue code cannot use -fobjc-arc, so we need to rely on -fblocks only. --- Libraries/LibCore/CMakeLists.txt | 10 +++++++++- Libraries/LibCore/EventSwift.mm | 8 +------- Meta/CMake/Swift/InitializeSwift.cmake | 3 +++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Libraries/LibCore/CMakeLists.txt b/Libraries/LibCore/CMakeLists.txt index dfb08110aec..d35df5055cb 100644 --- a/Libraries/LibCore/CMakeLists.txt +++ b/Libraries/LibCore/CMakeLists.txt @@ -122,12 +122,20 @@ if (ENABLE_SWIFT) else() list(APPEND SWIFT_EXCLUDE_HEADERS "EventLoopImplementationWindows.h") endif() + if (NOT APPLE) + list(APPEND SWIFT_EXCLUDE_HEADERS + IOSurface.h + MachPort.h + MachMessageTypes.h + ProcessStatisticsMach.h + ) + endif() generate_clang_module_map(LibCore EXCLUDE_FILES ${SWIFT_EXCLUDE_HEADERS}) target_sources(LibCore PRIVATE EventSwift.mm EventLoopExecutor.swift) - set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fblocks) target_link_libraries(LibCore PRIVATE AK) add_swift_target_properties(LibCore LAGOM_LIBRARIES AK) endif() diff --git a/Libraries/LibCore/EventSwift.mm b/Libraries/LibCore/EventSwift.mm index 871d8bf6bda..e7f866bea82 100644 --- a/Libraries/LibCore/EventSwift.mm +++ b/Libraries/LibCore/EventSwift.mm @@ -7,16 +7,10 @@ #include #include -#if !__has_feature(objc_arc) -# error "This file requires ARC" -#endif - namespace Core { void deferred_invoke_block(EventLoop& event_loop, void (^invokee)(void)) { - event_loop.deferred_invoke([invokee = move(invokee)] { - invokee(); - }); + event_loop.deferred_invoke(invokee); } } diff --git a/Meta/CMake/Swift/InitializeSwift.cmake b/Meta/CMake/Swift/InitializeSwift.cmake index 532c23767bd..1fa7e240d37 100644 --- a/Meta/CMake/Swift/InitializeSwift.cmake +++ b/Meta/CMake/Swift/InitializeSwift.cmake @@ -104,6 +104,9 @@ function(_setup_swift_paths) NO_DEFAULT_PATH) add_link_options("$<$:${SWIFT_SWIFTRT_FILE}>") endif() + + # FIXME: Re-enable SIL verification after https://github.com/swiftlang/swift/issues/80065 is fixed + add_compile_options("SHELL:$<$:-Xfrontend -sil-verify-none>") endfunction() _setup_swift_paths() From 564cd1849b995da9b7c991eca43a4afcb7c7d86a Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 18 Mar 2025 21:55:17 +0100 Subject: [PATCH 042/141] Tests: Disable `css/css-lists/list-style-type-string-004.html` 259d39cb was not enough to make this test pass reliably, so let's disable it back for now. --- Tests/LibWeb/TestConfig.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini index 833ee76dbd3..b71a11b7152 100644 --- a/Tests/LibWeb/TestConfig.ini +++ b/Tests/LibWeb/TestConfig.ini @@ -12,6 +12,7 @@ Ref/input/unicode-range.html Text/input/Crypto/SubtleCrypto-exportKey.html Text/input/Crypto/SubtleCrypto-generateKey.html Text/input/wpt-import/css/css-flexbox/text-as-flexitem-size-001.html +Ref/input/wpt-import/css/css-lists/list-style-type-string-004.html ; Animation tests are flaky Text/input/css/cubic-bezier-infinite-slope-crash.html From 02236be737fe30d4d92f7797f8699cf1a1d5e3e7 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Tue, 26 Nov 2024 15:43:02 +0000 Subject: [PATCH 043/141] LibWeb/CSP: Implement SecurityPolicyViolationEvent This is used to report violations of policies to the element/global object that caused it. --- Libraries/LibWeb/CMakeLists.txt | 1 + .../SecurityPolicyViolationEvent.cpp | 50 +++++++++++++ .../SecurityPolicyViolationEvent.h | 71 +++++++++++++++++++ .../SecurityPolicyViolationEvent.idl | 40 +++++++++++ Libraries/LibWeb/Forward.h | 2 + Libraries/LibWeb/idl_files.cmake | 1 + .../BindingsGenerator/IDLGenerators.cpp | 1 + .../LibWeb/BindingsGenerator/Namespaces.h | 1 + .../Text/expected/all-window-properties.txt | 1 + 9 files changed, 168 insertions(+) create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.h create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.idl diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index ab4e1a6cc79..33264d511a7 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -42,6 +42,7 @@ set(SOURCES ContentSecurityPolicy/Directives/SerializedDirective.cpp ContentSecurityPolicy/Policy.cpp ContentSecurityPolicy/PolicyList.cpp + ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp ContentSecurityPolicy/SerializedPolicy.cpp CredentialManagement/Credential.cpp CredentialManagement/CredentialsContainer.cpp diff --git a/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp b/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp new file mode 100644 index 00000000000..805b71c17bc --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::ContentSecurityPolicy { + +GC_DEFINE_ALLOCATOR(SecurityPolicyViolationEvent); + +GC::Ref SecurityPolicyViolationEvent::create(JS::Realm& realm, FlyString const& event_name, SecurityPolicyViolationEventInit const& event_init) +{ + return realm.create(realm, event_name, event_init); +} + +WebIDL::ExceptionOr> SecurityPolicyViolationEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, SecurityPolicyViolationEventInit const& event_init) +{ + return realm.create(realm, event_name, event_init); +} + +SecurityPolicyViolationEvent::SecurityPolicyViolationEvent(JS::Realm& realm, FlyString const& event_name, SecurityPolicyViolationEventInit const& event_init) + : Event(realm, event_name, event_init) + , m_document_uri(event_init.document_uri) + , m_referrer(event_init.referrer) + , m_blocked_uri(event_init.blocked_uri) + , m_violated_directive(event_init.violated_directive) + , m_effective_directive(event_init.effective_directive) + , m_original_policy(event_init.original_policy) + , m_source_file(event_init.source_file) + , m_sample(event_init.sample) + , m_disposition(event_init.disposition) + , m_status_code(event_init.status_code) + , m_line_number(event_init.line_number) + , m_column_number(event_init.column_number) +{ +} + +SecurityPolicyViolationEvent::~SecurityPolicyViolationEvent() = default; + +void SecurityPolicyViolationEvent::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(SecurityPolicyViolationEvent); +} + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.h b/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.h new file mode 100644 index 00000000000..1c2867a3495 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::ContentSecurityPolicy { + +struct SecurityPolicyViolationEventInit final : public DOM::EventInit { + String document_uri; + String referrer; + String blocked_uri; + String violated_directive; + String effective_directive; + String original_policy; + String source_file; + String sample; + Bindings::SecurityPolicyViolationEventDisposition disposition { Bindings::SecurityPolicyViolationEventDisposition::Enforce }; + u16 status_code { 0 }; + u32 line_number { 0 }; + u32 column_number { 0 }; +}; + +class SecurityPolicyViolationEvent final : public DOM::Event { + WEB_PLATFORM_OBJECT(SecurityPolicyViolationEvent, DOM::Event); + GC_DECLARE_ALLOCATOR(SecurityPolicyViolationEvent); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, FlyString const& event_name, SecurityPolicyViolationEventInit const& = {}); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, FlyString const& event_name, SecurityPolicyViolationEventInit const& event_init); + + virtual ~SecurityPolicyViolationEvent() override; + + String const& document_uri() const { return m_document_uri; } + String const& referrer() const { return m_referrer; } + String const& blocked_uri() const { return m_blocked_uri; } + String const& violated_directive() const { return m_violated_directive; } + String const& effective_directive() const { return m_effective_directive; } + String const& original_policy() const { return m_original_policy; } + String const& source_file() const { return m_source_file; } + String const& sample() const { return m_sample; } + Bindings::SecurityPolicyViolationEventDisposition disposition() const { return m_disposition; } + u16 status_code() const { return m_status_code; } + u32 line_number() const { return m_line_number; } + u32 column_number() const { return m_column_number; } + +private: + SecurityPolicyViolationEvent(JS::Realm&, FlyString const& event_name, SecurityPolicyViolationEventInit const&); + + virtual void initialize(JS::Realm&) override; + + String m_document_uri; + String m_referrer; + String m_blocked_uri; + String m_violated_directive; + String m_effective_directive; + String m_original_policy; + String m_source_file; + String m_sample; + Bindings::SecurityPolicyViolationEventDisposition m_disposition { Bindings::SecurityPolicyViolationEventDisposition::Enforce }; + u16 m_status_code { 0 }; + u32 m_line_number { 0 }; + u32 m_column_number { 0 }; +}; + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.idl b/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.idl new file mode 100644 index 00000000000..c73dc935993 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/SecurityPolicyViolationEvent.idl @@ -0,0 +1,40 @@ +#import + +// https://w3c.github.io/webappsec-csp/#enumdef-securitypolicyviolationeventdisposition +enum SecurityPolicyViolationEventDisposition { + "enforce", + "report" +}; + +// https://w3c.github.io/webappsec-csp/#securitypolicyviolationevent +[Exposed=(Window,Worker)] +interface SecurityPolicyViolationEvent : Event { + constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict = {}); + readonly attribute USVString documentURI; + readonly attribute USVString referrer; + readonly attribute USVString blockedURI; + readonly attribute DOMString effectiveDirective; + readonly attribute DOMString violatedDirective; // historical alias of effectiveDirective + readonly attribute DOMString originalPolicy; + readonly attribute USVString sourceFile; + readonly attribute DOMString sample; + readonly attribute SecurityPolicyViolationEventDisposition disposition; + readonly attribute unsigned short statusCode; + readonly attribute unsigned long lineNumber; + readonly attribute unsigned long columnNumber; +}; + +dictionary SecurityPolicyViolationEventInit : EventInit { + USVString documentURI = ""; + USVString referrer = ""; + USVString blockedURI = ""; + DOMString violatedDirective = ""; + DOMString effectiveDirective = ""; + DOMString originalPolicy = ""; + USVString sourceFile = ""; + DOMString sample = ""; + SecurityPolicyViolationEventDisposition disposition = "enforce"; + unsigned short statusCode = 0; + unsigned long lineNumber = 0; + unsigned long columnNumber = 0; +}; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 8389057b448..f93dc380a7d 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -102,6 +102,8 @@ class DecompressionStream; namespace Web::ContentSecurityPolicy { class Policy; class PolicyList; +class SecurityPolicyViolationEvent; +struct SecurityPolicyViolationEventInit; struct SerializedPolicy; } diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 487a78ef9d4..d12820fbe1e 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -10,6 +10,7 @@ libweb_js_bindings(Animations/KeyframeEffect) libweb_js_bindings(Clipboard/Clipboard) libweb_js_bindings(Clipboard/ClipboardEvent) libweb_js_bindings(Clipboard/ClipboardItem) +libweb_js_bindings(ContentSecurityPolicy/SecurityPolicyViolationEvent) libweb_js_bindings(Compression/CompressionStream) libweb_js_bindings(Compression/DecompressionStream) libweb_js_bindings(CredentialManagement/Credential) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 4fdc25356d7..5763a99856a 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -4501,6 +4501,7 @@ static void generate_using_namespace_definitions(SourceGenerator& generator) // FIXME: This is a total hack until we can figure out the namespace for a given type somehow. using namespace Web::Animations; using namespace Web::Clipboard; +using namespace Web::ContentSecurityPolicy; using namespace Web::CredentialManagement; using namespace Web::Crypto; using namespace Web::CSS; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h index fce40c45351..c9dbf2167c5 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h @@ -16,6 +16,7 @@ static constexpr Array libweb_interface_namespaces = { "CSS"sv, "Clipboard"sv, "Compression"sv, + "ContentSecurityPolicy"sv, "Crypto"sv, "DOM"sv, "DOMURL"sv, diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index d15c07921ff..787ba16db73 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -358,6 +358,7 @@ SVGTransformList SVGUseElement Screen ScreenOrientation +SecurityPolicyViolationEvent Selection ServiceWorker ServiceWorkerContainer From 86170f4bfd381a81582f37a5f2f0234303193a65 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Mon, 25 Nov 2024 17:22:08 +0000 Subject: [PATCH 044/141] LibWeb/CSP: Introduce the ability to create and report a violation A violation provides several details about an enforcement failing, such as the URL of the document, the directive that returned "Blocked", etc. --- Libraries/LibWeb/CMakeLists.txt | 2 + .../Directives/DirectiveOperations.cpp | 193 +++++++ .../Directives/DirectiveOperations.h | 24 + .../LibWeb/ContentSecurityPolicy/Policy.cpp | 16 + .../LibWeb/ContentSecurityPolicy/Policy.h | 8 + .../SerializedPolicy.cpp | 2 + .../ContentSecurityPolicy/SerializedPolicy.h | 1 + .../ContentSecurityPolicy/Violation.cpp | 480 ++++++++++++++++++ .../LibWeb/ContentSecurityPolicy/Violation.h | 141 +++++ Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/Infra/JSON.cpp | 105 ++++ Libraries/LibWeb/Infra/JSON.h | 9 + 12 files changed, 982 insertions(+) create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.cpp create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/Violation.cpp create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/Violation.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 33264d511a7..07d1daea425 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -38,12 +38,14 @@ set(SOURCES Compression/DecompressionStream.cpp ContentSecurityPolicy/Directives/Directive.cpp ContentSecurityPolicy/Directives/DirectiveFactory.cpp + ContentSecurityPolicy/Directives/DirectiveOperations.cpp ContentSecurityPolicy/Directives/Names.cpp ContentSecurityPolicy/Directives/SerializedDirective.cpp ContentSecurityPolicy/Policy.cpp ContentSecurityPolicy/PolicyList.cpp ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp ContentSecurityPolicy/SerializedPolicy.cpp + ContentSecurityPolicy/Violation.cpp CredentialManagement/Credential.cpp CredentialManagement/CredentialsContainer.cpp CredentialManagement/FederatedCredential.cpp diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.cpp b/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.cpp new file mode 100644 index 00000000000..5d5e01995ed --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::ContentSecurityPolicy::Directives { + +// https://w3c.github.io/webappsec-csp/#directive-fallback-list +// Will return an ordered set of the fallback directives for a specific directive. +// The returned ordered set is sorted from most relevant to least relevant and it includes the effective directive +// itself. +static HashMap> fetch_directive_fallback_list { + // "script-src-elem" + // 1. Return << "script-src-elem", "script-src", "default-src" >>. + { "script-src-elem"sv, { "script-src-elem"sv, "script-src"sv, "default-src"sv } }, + + // "script-src-attr" + // 1. Return << "script-src-attr", "script-src", "default-src" >>. + { "script-src-attr"sv, { "script-src-attr"sv, "script-src"sv, "default-src"sv } }, + + // "style-src-elem" + // 1. Return << "style-src-elem", "style-src", "default-src" >>. + { "style-src-elem"sv, { "style-src-elem"sv, "style-src"sv, "default-src"sv } }, + + // "style-src-attr" + // 1. Return << "style-src-attr", "style-src", "default-src" >>. + { "style-src-attr"sv, { "style-src-attr"sv, "style-src"sv, "default-src"sv } }, + + // "worker-src" + // 1. Return << "worker-src", "child-src", "script-src", "default-src" >>. + { "worker-src"sv, { "worker-src"sv, "child-src"sv, "script-src"sv, "default-src"sv } }, + + // "connect-src" + // 1. Return << "connect-src", "default-src" >>. + { "connect-src"sv, { "connect-src"sv, "default-src"sv } }, + + // "manifest-src" + // 1. Return << "manifest-src", "default-src" >>. + { "manifest-src"sv, { "manifest-src"sv, "default-src"sv } }, + + // "object-src" + // 1. Return << "object-src", "default-src" >>. + { "object-src"sv, { "object-src"sv, "default-src"sv } }, + + // "frame-src" + // 1. Return << "frame-src", "child-src", "default-src" >>. + { "frame-src"sv, { "frame-src"sv, "child-src"sv, "default-src"sv } }, + + // "media-src" + // 1. Return << "media-src", "default-src" >>. + { "media-src"sv, { "media-src"sv, "default-src"sv } }, + + // "font-src" + // 1. Return << "font-src", "default-src" >>. + { "font-src"sv, { "font-src"sv, "default-src"sv } }, + + // "img-src" + // 1. Return << "img-src", "default-src" >>. + { "img-src"sv, { "img-src"sv, "default-src"sv } }, +}; + +// https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request +Optional get_the_effective_directive_for_request(GC::Ref request) +{ + // Each fetch directive controls a specific destination of request. Given a request request, the following algorithm + // returns either null or the name of the request’s effective directive: + // 1. If request’s initiator is "prefetch" or "prerender", return default-src. + if (request->initiator() == Fetch::Infrastructure::Request::Initiator::Prefetch || request->initiator() == Fetch::Infrastructure::Request::Initiator::Prerender) + return Names::DefaultSrc; + + // 2. Switch on request’s destination, and execute the associated steps: + // the empty string + // 1. Return connect-src. + if (!request->destination().has_value()) + return Names::ConnectSrc; + + switch (request->destination().value()) { + // "manifest" + // 1. Return manifest-src. + case Fetch::Infrastructure::Request::Destination::Manifest: + return Names::ManifestSrc; + // "object" + // "embed" + // 1. Return object-src. + case Fetch::Infrastructure::Request::Destination::Object: + case Fetch::Infrastructure::Request::Destination::Embed: + return Names::ObjectSrc; + // "frame" + // "iframe" + // 1. Return frame-src. + case Fetch::Infrastructure::Request::Destination::Frame: + case Fetch::Infrastructure::Request::Destination::IFrame: + return Names::FrameSrc; + // "audio" + // "track" + // "video" + // 1. Return media-src. + case Fetch::Infrastructure::Request::Destination::Audio: + case Fetch::Infrastructure::Request::Destination::Track: + case Fetch::Infrastructure::Request::Destination::Video: + return Names::MediaSrc; + // "font" + // 1. Return font-src. + case Fetch::Infrastructure::Request::Destination::Font: + return Names::FontSrc; + // "image" + // 1. Return img-src. + case Fetch::Infrastructure::Request::Destination::Image: + return Names::ImgSrc; + // "style" + // 1. Return style-src-elem. + case Fetch::Infrastructure::Request::Destination::Style: + return Names::StyleSrcElem; + // "script" + // "xslt" + // "audioworklet" + // "paintworklet" + // 1. Return script-src-elem. + case Fetch::Infrastructure::Request::Destination::Script: + case Fetch::Infrastructure::Request::Destination::XSLT: + case Fetch::Infrastructure::Request::Destination::AudioWorklet: + case Fetch::Infrastructure::Request::Destination::PaintWorklet: + return Names::ScriptSrcElem; + // "serviceworker" + // "sharedworker" + // "worker" + // 1. Return worker-src. + case Fetch::Infrastructure::Request::Destination::ServiceWorker: + case Fetch::Infrastructure::Request::Destination::SharedWorker: + case Fetch::Infrastructure::Request::Destination::Worker: + return Names::WorkerSrc; + // "json" + // "webidentity" + // 1. Return connect-src. + case Fetch::Infrastructure::Request::Destination::JSON: + case Fetch::Infrastructure::Request::Destination::WebIdentity: + return Names::ConnectSrc; + // "report" + // 1. Return null. + case Fetch::Infrastructure::Request::Destination::Report: + return OptionalNone {}; + // 3. Return connect-src. + // Spec Note: The algorithm returns connect-src as a default fallback. This is intended for new fetch destinations + // that are added and which don’t explicitly fall into one of the other categories. + default: + return Names::ConnectSrc; + } +} + +// https://w3c.github.io/webappsec-csp/#directive-fallback-list +Vector get_fetch_directive_fallback_list(Optional directive_name) +{ + if (!directive_name.has_value()) + return {}; + + auto list_iterator = fetch_directive_fallback_list.find(directive_name.value()); + if (list_iterator == fetch_directive_fallback_list.end()) + return {}; + + return list_iterator->value; +} + +// https://w3c.github.io/webappsec-csp/#should-directive-execute +ShouldExecute should_fetch_directive_execute(Optional effective_directive_name, FlyString const& directive_name, GC::Ref policy) +{ + // 1. Let directive fallback list be the result of executing § 6.8.3 Get fetch directive fallback list on effective + // directive name. + auto const& directive_fallback_list = get_fetch_directive_fallback_list(effective_directive_name); + + // 2. For each fallback directive of directive fallback list: + for (auto fallback_directive : directive_fallback_list) { + // 1. If directive name is fallback directive, Return "Yes". + if (directive_name == fallback_directive) + return ShouldExecute::Yes; + + // 2. If policy contains a directive whose name is fallback directive, Return "No". + if (policy->contains_directive_with_name(fallback_directive)) + return ShouldExecute::No; + } + + // 3. Return "No". + return ShouldExecute::No; +} + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h b/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h new file mode 100644 index 00000000000..05e3b4950c5 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::ContentSecurityPolicy::Directives { + +enum class ShouldExecute { + No, + Yes, +}; + +[[nodiscard]] Optional get_the_effective_directive_for_request(GC::Ref request); +[[nodiscard]] Vector get_fetch_directive_fallback_list(Optional directive_name); +[[nodiscard]] ShouldExecute should_fetch_directive_execute(Optional effective_directive_name, FlyString const& directive_name, GC::Ref policy); + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Policy.cpp b/Libraries/LibWeb/ContentSecurityPolicy/Policy.cpp index 3dbcb2e17f1..16a2e50c61b 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Policy.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/Policy.cpp @@ -35,6 +35,7 @@ GC::Ref Policy::parse_a_serialized_csp(JS::Realm& realm, Variant(); + policy->m_pre_parsed_policy_string = serialized_string; policy->m_source = source; policy->m_disposition = disposition; @@ -148,6 +149,7 @@ GC::Ref Policy::create_from_serialized_policy(JS::Realm& realm, Serializ policy->m_disposition = serialized_policy.disposition; policy->m_source = serialized_policy.source; policy->m_self_origin = serialized_policy.self_origin; + policy->m_pre_parsed_policy_string = serialized_policy.pre_parsed_policy_string; return policy; } @@ -159,6 +161,18 @@ bool Policy::contains_directive_with_name(StringView name) const return !maybe_directive.is_end(); } +GC::Ptr Policy::get_directive_by_name(StringView name) const +{ + auto maybe_directive = m_directives.find_if([name](auto const& directive) { + return directive->name() == name; + }); + + if (!maybe_directive.is_end()) + return *maybe_directive; + + return nullptr; +} + GC::Ref Policy::clone(JS::Realm& realm) const { auto policy = realm.create(); @@ -171,6 +185,7 @@ GC::Ref Policy::clone(JS::Realm& realm) const policy->m_disposition = m_disposition; policy->m_source = m_source; policy->m_self_origin = m_self_origin; + policy->m_pre_parsed_policy_string = m_pre_parsed_policy_string; return policy; } @@ -187,6 +202,7 @@ SerializedPolicy Policy::serialize() const .disposition = m_disposition, .source = m_source, .self_origin = m_self_origin, + .pre_parsed_policy_string = m_pre_parsed_policy_string, }; } diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Policy.h b/Libraries/LibWeb/ContentSecurityPolicy/Policy.h index 6d33dc01ed6..5a73199270c 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Policy.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/Policy.h @@ -46,8 +46,10 @@ public: [[nodiscard]] Disposition disposition() const { return m_disposition; } [[nodiscard]] Source source() const { return m_source; } [[nodiscard]] URL::Origin const& self_origin() const { return m_self_origin; } + [[nodiscard]] String const& pre_parsed_policy_string(Badge) const { return m_pre_parsed_policy_string; } [[nodiscard]] bool contains_directive_with_name(StringView name) const; + [[nodiscard]] GC::Ptr get_directive_by_name(StringView) const; [[nodiscard]] GC::Ref clone(JS::Realm&) const; [[nodiscard]] SerializedPolicy serialize() const; @@ -77,6 +79,12 @@ private: // their policy but have an opaque origin. Most of the time this will simply be the environment settings // object’s origin. URL::Origin m_self_origin; + + // This is used for reporting which policy was violated. It's not exactly specified, only linking to an ABNF grammar + // definition. WebKit and Blink return the original string that was parsed, whereas Firefox seems to try and return + // a nice serialization of what it parsed. For simplicity and wider compatibility, we follow what WebKit and Blink + // do. + String m_pre_parsed_policy_string; }; } diff --git a/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.cpp b/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.cpp index 42ce6a2e322..a466a2d5abd 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.cpp @@ -17,6 +17,7 @@ ErrorOr encode(Encoder& encoder, Web::ContentSecurityPolicy::SerializedPol TRY(encoder.encode(serialized_policy.disposition)); TRY(encoder.encode(serialized_policy.source)); TRY(encoder.encode(serialized_policy.self_origin)); + TRY(encoder.encode(serialized_policy.pre_parsed_policy_string)); return {}; } @@ -30,6 +31,7 @@ ErrorOr decode(Decoder& decoder) serialized_policy.disposition = TRY(decoder.decode()); serialized_policy.source = TRY(decoder.decode()); serialized_policy.self_origin = TRY(decoder.decode()); + serialized_policy.pre_parsed_policy_string = TRY(decoder.decode()); return serialized_policy; } diff --git a/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.h b/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.h index a6dbebceeb7..0cd289d5c68 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/SerializedPolicy.h @@ -17,6 +17,7 @@ struct SerializedPolicy { Policy::Disposition disposition; Policy::Source source; URL::Origin self_origin; + String pre_parsed_policy_string; }; } diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Violation.cpp b/Libraries/LibWeb/ContentSecurityPolicy/Violation.cpp new file mode 100644 index 00000000000..5eb9b7d831d --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/Violation.cpp @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::ContentSecurityPolicy { + +GC_DEFINE_ALLOCATOR(Violation); + +Violation::Violation(GC::Ptr global_object, GC::Ref policy, String directive) + : m_global_object(global_object) + , m_policy(policy) + , m_effective_directive(directive) +{ +} + +// https://w3c.github.io/webappsec-csp/#create-violation-for-global +GC::Ref Violation::create_a_violation_object_for_global_policy_and_directive(JS::Realm& realm, GC::Ptr global_object, GC::Ref policy, String directive) +{ + // 1. Let violation be a new violation whose global object is global, policy is policy, effective directive is + // directive, and resource is null. + auto violation = realm.create(global_object, policy, directive); + + // FIXME: 2. If the user agent is currently executing script, and can extract a source file’s URL, line number, + // and column number from the global, set violation’s source file, line number, and column number + // accordingly. + // SPEC ISSUE 1: Is this kind of thing specified anywhere? I didn’t see anything that looked useful in [ECMA262]. + + // 3. If global is a Window object, set violation’s referrer to global’s document's referrer. + if (global_object) { + if (auto* window = dynamic_cast(global_object.ptr())) { + violation->m_referrer = URL::Parser::basic_parse(window->associated_document().referrer()); + } + } + + // FIXME: 4. Set violation’s status to the HTTP status code for the resource associated with violation’s global object. + // SPEC ISSUE 2: How, exactly, do we get the status code? We don’t actually store it anywhere. + + // 5. Return violation. + return violation; +} + +// https://w3c.github.io/webappsec-csp/#create-violation-for-request +GC::Ref Violation::create_a_violation_object_for_request_and_policy(JS::Realm& realm, GC::Ref request, GC::Ref policy) +{ + // 1. Let directive be the result of executing § 6.8.1 Get the effective directive for request on request. + auto directive = Directives::get_the_effective_directive_for_request(request); + + // NOTE: The spec assumes that the effective directive of a Violation is a non-empty string. + // See the definition of m_effective_directive. + VERIFY(directive.has_value()); + + // 2. Let violation be the result of executing § 2.4.1 Create a violation object for global, policy, and directive + // on request’s client’s global object, policy, and directive. + auto violation = create_a_violation_object_for_global_policy_and_directive(realm, request->client()->global_object(), policy, directive->to_string()); + + // 3. Set violation’s resource to request’s url. + // Spec Note: We use request’s url, and not its current url, as the latter might contain information about redirect + // targets to which the page MUST NOT be given access. + violation->m_resource = request->url(); + + // 4. Return violation. + return violation; +} + +void Violation::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_global_object); + visitor.visit(m_policy); + visitor.visit(m_element); +} + +// https://w3c.github.io/webappsec-csp/#violation-url +URL::URL Violation::url() const +{ + // Each violation has a url which is its global object’s URL. + if (!m_global_object) { + // FIXME: What do we return here? + dbgln("FIXME: Figure out URL for violation with null global object."); + return URL::URL {}; + } + + // FIXME: File a spec issue about what to do for ShadowRealms here. + auto* universal_scope = dynamic_cast(m_global_object.ptr()); + VERIFY(universal_scope); + auto& principal_global = HTML::relevant_principal_global_object(universal_scope->this_impl()); + + if (auto* window = dynamic_cast(&principal_global)) { + return window->associated_document().url(); + } + + if (auto* worker = dynamic_cast(&principal_global)) { + return worker->url(); + } + + TODO(); +} + +// https://w3c.github.io/webappsec-csp/#strip-url-for-use-in-reports +[[nodiscard]] static String strip_url_for_use_in_reports(URL::URL url) +{ + // 1. If url’s scheme is not an HTTP(S) scheme, then return url’s scheme. + if (!Fetch::Infrastructure::is_http_or_https_scheme(url.scheme())) + return url.scheme(); + + // 2. Set url’s fragment to the empty string. + // FIXME: File spec issue about potentially meaning `null` here, as using empty string leaves a stray # at the end. + url.set_fragment(OptionalNone {}); + + // 3. Set url’s username to the empty string. + url.set_username(String {}); + + // 4. Set url’s password to the empty string. + url.set_password(String {}); + + // 5. Return the result of executing the URL serializer on url. + return url.serialize(); +} + +// https://w3c.github.io/webappsec-csp/#obtain-violation-blocked-uri +String Violation::obtain_the_blocked_uri_of_resource() const +{ + // 1. Assert: resource is a URL or a string. + VERIFY(m_resource.has() || m_resource.has()); + + // 2. If resource is a URL, return the result of executing § 5.4 Strip URL for use in reports on resource. + if (m_resource.has()) { + auto const& url = m_resource.get(); + return strip_url_for_use_in_reports(url); + } + + // 3. Return resource. + auto resource = m_resource.get(); + switch (resource) { +#define __ENUMERATE_RESOURCE_TYPE(type, value) \ + case Resource::type: \ + return value##_string; + ENUMERATE_RESOURCE_TYPES +#undef __ENUMERATE_RESOURCE_TYPE + default: + VERIFY_NOT_REACHED(); + } +} + +[[nodiscard]] static String original_disposition_to_string(Policy::Disposition disposition) +{ + switch (disposition) { +#define __ENUMERATE_DISPOSITION_TYPE(type, value) \ + case Policy::Disposition::type: \ + return value##_string; + ENUMERATE_DISPOSITION_TYPES +#undef __ENUMERATE_DISPOSITION_TYPE + default: + VERIFY_NOT_REACHED(); + } +} + +// https://w3c.github.io/webappsec-csp/#deprecated-serialize-violation +ByteBuffer Violation::obtain_the_deprecated_serialization(JS::Realm& realm) const +{ + // 1. Let body be a map with its keys initialized as follows: + Infra::JSONObject body; + + // "document-uri" + // The result of executing § 5.4 Strip URL for use in reports on violation's url. + body.value.set("document-uri"_string, Infra::JSONValue { strip_url_for_use_in_reports(url()) }); + + // "referrer" + // The result of executing § 5.4 Strip URL for use in reports on violation's referrer. + // FIXME: File spec issue that referrer can be null here. + Infra::JSONValue referrer = m_referrer.has_value() + ? Infra::JSONValue { strip_url_for_use_in_reports(m_referrer.value()) } + : Infra::JSONValue { Empty {} }; + + body.value.set("referrer"_string, referrer); + + // "blocked-uri" + // The result of executing § 5.2 Obtain the blockedURI of a violation’s resource on violation’s resource. + body.value.set("blocked_uri"_string, Infra::JSONValue { obtain_the_blocked_uri_of_resource() }); + + // "effective-directive" + // violation's effective directive + body.value.set("effective-directive"_string, Infra::JSONValue { m_effective_directive }); + + // "violated-directive" + // violation's effective directive + body.value.set("violated-directive"_string, Infra::JSONValue { m_effective_directive }); + + // "original-policy" + // The serialization of violation's policy + body.value.set("original-policy"_string, Infra::JSONValue { m_policy->pre_parsed_policy_string({}) }); + + // "disposition" + // The disposition of violation's policy + body.value.set("disposition"_string, Infra::JSONValue { original_disposition_to_string(disposition()) }); + + // "status-code" + // violation's status + body.value.set("status-code"_string, Infra::JSONValue { m_status }); + + // "script-sample" + // violation's sample + // Spec Note: The name script-sample was chosen for compatibility with an earlier iteration of this feature which + // has shipped in Firefox since its initial implementation of CSP. Despite the name, this field will + // contain samples for non-script violations, like stylesheets. The data contained in a + // SecurityPolicyViolationEvent object, and in reports generated via the new report-to directive, is + // named in a more encompassing fashion: sample. + body.value.set("script-sample"_string, Infra::JSONValue { m_sample }); + + // 2. If violation’s source file is not null: + if (m_source_file.has_value()) { + // 1. Set body["source-file'] to the result of executing § 5.4 Strip URL for use in reports on violation’s + // source file. + body.value.set("source-file"_string, Infra::JSONValue { strip_url_for_use_in_reports(m_source_file.value()) }); + + // 2. Set body["line-number"] to violation’s line number. + body.value.set("line-number"_string, Infra::JSONValue { m_line_number }); + + // 3. Set body["column-number"] to violation’s column number. + body.value.set("column-number"_string, Infra::JSONValue { m_column_number }); + } + + // 3. Assert: If body["blocked-uri"] is not "inline", then body["sample"] is the empty string. + // FIXME: File spec issue that body["sample"] should be body["script-sample"] + if (m_resource.has() && m_resource.get() != Resource::Inline) { + VERIFY(m_sample.is_empty()); + } + + // 4. Return the result of serialize an infra value to JSON bytes given «[ "csp-report" → body ]». + Infra::JSONObject csp_report; + csp_report.value.set("csp-report"_string, Infra::JSONObject { move(body) }); + + HTML::TemporaryExecutionContext execution_context { realm }; + return Infra::serialize_an_infra_value_to_json_bytes(realm, move(csp_report)); +} + +[[nodiscard]] static Bindings::SecurityPolicyViolationEventDisposition original_disposition_to_bindings_disposition(Policy::Disposition disposition) +{ + switch (disposition) { +#define __ENUMERATE_DISPOSITION_TYPE(type, _) \ + case Policy::Disposition::type: \ + return Bindings::SecurityPolicyViolationEventDisposition::type; + ENUMERATE_DISPOSITION_TYPES +#undef __ENUMERATE_DISPOSITION_TYPE + default: + VERIFY_NOT_REACHED(); + } +} + +// https://w3c.github.io/webappsec-csp/#report-violation +void Violation::report_a_violation(JS::Realm& realm) +{ + dbgln("Content Security Policy violation{}: Refusing access to resource '{}' because it does not appear in the '{}' directive.", + disposition() == Policy::Disposition::Report ? " (report only)"sv : ""sv, + obtain_the_blocked_uri_of_resource(), + m_effective_directive); + + // 1. Let global be violation’s global object. + auto global = m_global_object; + + // 2. Let target be violation’s element. + auto target = m_element; + + // 3. Queue a task to run the following steps: + // Spec Note: We "queue a task" here to ensure that the event targeting and dispatch happens after JavaScript + // completes execution of the task responsible for a given violation (which might manipulate the DOM). + HTML::queue_a_task(HTML::Task::Source::Unspecified, nullptr, nullptr, GC::create_function(realm.heap(), [this, global, target, &realm] { + auto& vm = realm.vm(); + + GC::Ptr target_as_object = target; + + // 1. If target is not null, and global is a Window, and target’s shadow-including root is not global’s + // associated Document, set target to null. + // Spec Note: This ensures that we fire events only at elements connected to violation’s policy’s Document. + // If a violation is caused by an element which isn’t connected to that document, we’ll fire the + // event at the document rather than the element in order to ensure that the violation is visible + // to the document’s listeners. + if (target && is(global.ptr())) { + auto const& window = static_cast(*global.ptr()); + if (&target->shadow_including_root() != &window.associated_document()) + target_as_object = nullptr; + } + + // 2. If target is null: + if (!target_as_object) { + // 1. Set target to violation’s global object. + target_as_object = m_global_object; + + // 2. If target is a Window, set target to target’s associated Document. + if (is(target_as_object.ptr())) { + auto& window = static_cast(*target_as_object.ptr()); + target_as_object = window.associated_document(); + } + } + + // 3. If target implements EventTarget, fire an event named securitypolicyviolation that uses the + // SecurityPolicyViolationEvent interface at target with its attributes initialized as follows: + if (is(target_as_object.ptr())) { + auto& event_target = static_cast(*target_as_object.ptr()); + + SecurityPolicyViolationEventInit event_init {}; + + // bubbles + // true + event_init.bubbles = true; + + // composed + // true + // Spec Note: We set the composed attribute, which means that this event can be captured on its way + // into, and will bubble its way out of a shadow tree. target, et al will be automagically + // scoped correctly for the main tree. + event_init.composed = true; + + // documentURI + // The result of executing § 5.4 Strip URL for use in reports on violation's url. + event_init.document_uri = strip_url_for_use_in_reports(url()); + + // referrer + // The result of executing § 5.4 Strip URL for use in reports on violation's referrer. + // FIXME: File spec issue for referrer being potentially null. + event_init.referrer = m_referrer.has_value() ? strip_url_for_use_in_reports(m_referrer.value()) : String {}; + + // blockedURI + // The result of executing § 5.2 Obtain the blockedURI of a violation's resource on violation’s + // resource. + event_init.blocked_uri = obtain_the_blocked_uri_of_resource(); + + // effectiveDirective + // violation's effective directive + event_init.effective_directive = m_effective_directive; + + // violatedDirective + // violation's effective directive + // Spec Note: Both effectiveDirective and violatedDirective are the same value. This is intentional + // to maintain backwards compatibility. + event_init.violated_directive = m_effective_directive; + + // originalPolicy + // The serialization of violation's policy + event_init.original_policy = m_policy->pre_parsed_policy_string({}); + + // disposition + // violation's disposition + event_init.disposition = original_disposition_to_bindings_disposition(disposition()); + + // sourceFile + // The result of executing § 5.4 Strip URL for use in reports on violation’s source file, if + // violation's source file is not null, or null otherwise. + event_init.source_file = m_source_file.has_value() ? strip_url_for_use_in_reports(m_source_file.value()) : String {}; + + // statusCode + // violation's status + event_init.status_code = m_status; + + // lineNumber + // violation’s line number + event_init.line_number = m_line_number; + + // columnNumber + // violation’s column number + event_init.column_number = m_column_number; + + // sample + // violation's sample + event_init.sample = m_sample; + + auto event = SecurityPolicyViolationEvent::create(realm, HTML::EventNames::securitypolicyviolation, event_init); + event->set_is_trusted(true); + event_target.dispatch_event(event); + } + + // 4. If violation’s policy’s directive set contains a directive named "report-uri" directive: + if (auto report_uri_directive = m_policy->get_directive_by_name(Directives::Names::ReportUri)) { + // 1. If violation’s policy’s directive set contains a directive named "report-to", skip the remaining + // substeps. + if (!m_policy->contains_directive_with_name(Directives::Names::ReportTo)) { + // 1. For each token of directive’s value: + for (auto const& token : report_uri_directive->value()) { + // 1. Let endpoint be the result of executing the URL parser with token as the input, and + // violation’s url as the base URL. + auto endpoint = DOMURL::parse(token, url()); + + // 2. If endpoint is not a valid URL, skip the remaining substeps. + if (endpoint.has_value()) { + // 3. Let request be a new request, initialized as follows: + auto request = Fetch::Infrastructure::Request::create(vm); + + // method + // "POST" + request->set_method(MUST(ByteBuffer::copy("POST"sv.bytes()))); + + // url + // violation’s url + // FIXME: File spec issue that this is incorrect, it should be `endpoint` instead. + request->set_url(endpoint.value()); + + // origin + // violation's global object's relevant settings object's origin + // FIXME: File spec issue that global object can be null, so we use the realm to get the ESO + // instead, and cross ShadowRealm boundaries with the principal realm. + auto& environment_settings_object = Bindings::principal_host_defined_environment_settings_object(HTML::principal_realm(realm)); + request->set_origin(environment_settings_object.origin()); + + // window + // "no-window" + request->set_window(Fetch::Infrastructure::Request::Window::NoWindow); + + // client + // violation's global object's relevant settings object + request->set_client(&environment_settings_object); + + // destination + // "report" + request->set_destination(Fetch::Infrastructure::Request::Destination::Report); + + // initiator + // "" + request->set_initiator(OptionalNone {}); + + // credentials mode + // "same-origin" + request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::SameOrigin); + + // keepalive + // "true" + request->set_keepalive(true); + + // header list + // A header list containing a single header whose name is "Content-Type", and value is + // "application/csp-report" + auto header_list = Fetch::Infrastructure::HeaderList::create(vm); + auto content_type_header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "application/csp-report"sv); + header_list->append(move(content_type_header)); + request->set_header_list(header_list); + + // body + // The result of executing § 5.3 Obtain the deprecated serialization of violation on + // violation + request->set_body(obtain_the_deprecated_serialization(realm)); + + // redirect mode + // "error" + request->set_redirect_mode(Fetch::Infrastructure::Request::RedirectMode::Error); + + // 4. Fetch request. The result will be ignored. + (void)Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, {})); + } + } + } + + // 5. If violation's policy's directive set contains a directive named "report-to" directive: + if (auto report_to_directive = m_policy->get_directive_by_name(Directives::Names::ReportTo)) { + (void)report_to_directive; + dbgln("FIXME: Implement report-to directive in violation reporting"); + } + } + })); +} + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Violation.h b/Libraries/LibWeb/ContentSecurityPolicy/Violation.h new file mode 100644 index 00000000000..e09a9c83837 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/Violation.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::ContentSecurityPolicy { + +#define ENUMERATE_RESOURCE_TYPES \ + __ENUMERATE_RESOURCE_TYPE(Inline, "inline") \ + __ENUMERATE_RESOURCE_TYPE(Eval, "eval") \ + __ENUMERATE_RESOURCE_TYPE(WasmEval, "wasm-eval") \ + __ENUMERATE_RESOURCE_TYPE(TrustedTypesPolicy, "trusted-types-policy") \ + __ENUMERATE_RESOURCE_TYPE(TrustedTypesSink, "trusted-types-sink") + +// https://w3c.github.io/webappsec-csp/#violation +// A violation represents an action or resource which goes against the set of policy objects associated with a global +// object. +class Violation final : public JS::Cell { + GC_CELL(Violation, JS::Cell); + GC_DECLARE_ALLOCATOR(Violation); + +public: + enum class Resource { +#define __ENUMERATE_RESOURCE_TYPE(type, _) type, + ENUMERATE_RESOURCE_TYPES +#undef __ENUMERATE_RESOURCE_TYPE + }; + + using ResourceType = Variant; + + virtual ~Violation() = default; + + [[nodiscard]] static GC::Ref create_a_violation_object_for_global_policy_and_directive(JS::Realm& realm, GC::Ptr global_object, GC::Ref policy, String directive); + [[nodiscard]] static GC::Ref create_a_violation_object_for_request_and_policy(JS::Realm& realm, GC::Ref request, GC::Ref); + + // https://w3c.github.io/webappsec-csp/#violation-url + [[nodiscard]] URL::URL url() const; + + [[nodiscard]] u16 status() const { return m_status; } + void set_status(u16 status) { m_status = status; } + + [[nodiscard]] ResourceType const& resource() const { return m_resource; } + void set_resource(ResourceType resource) { m_resource = resource; } + + [[nodiscard]] Optional const& referrer() const { return m_referrer; } + + [[nodiscard]] Policy const& policy() const { return m_policy; } + + // https://w3c.github.io/webappsec-csp/#violation-disposition + [[nodiscard]] Policy::Disposition disposition() const { return m_policy->disposition(); } + + [[nodiscard]] String const& effective_directive() const { return m_effective_directive; } + + [[nodiscard]] Optional source_file() const { return m_source_file; } + void set_source_file(URL::URL source_file) { m_source_file = source_file; } + + [[nodiscard]] u32 line_number() const { return m_line_number; } + void set_line_number(u32 line_number) { m_line_number = line_number; } + + [[nodiscard]] u32 column_number() const { return m_column_number; } + void set_column_number(u32 column_number) { m_column_number = column_number; } + + [[nodiscard]] GC::Ptr element() const { return m_element; } + void set_element(GC::Ref element) { m_element = element; } + + [[nodiscard]] String const& sample() const { return m_sample; } + void set_sample(String sample) { m_sample = sample; } + + void report_a_violation(JS::Realm&); + +protected: + virtual void visit_edges(Cell::Visitor&) override; + +private: + Violation(GC::Ptr global_object, GC::Ref policy, String directive); + + [[nodiscard]] String obtain_the_blocked_uri_of_resource() const; + [[nodiscard]] ByteBuffer obtain_the_deprecated_serialization(JS::Realm&) const; + + // https://w3c.github.io/webappsec-csp/#violation-global-object + // Each violation has a global object, which is the global object whose policy has been violated. + GC::Ptr m_global_object; + + // https://w3c.github.io/webappsec-csp/#violation-status + // Each violation has a status which is a non-negative integer representing the HTTP status code of the resource + // for which the global object was instantiated. + u16 m_status { 0 }; + + // https://w3c.github.io/webappsec-csp/#violation-resource + // Each violation has a resource, which is either null, "inline", "eval", "wasm-eval", "trusted-types-policy" + // "trusted-types-sink" or a URL. It represents the resource which violated the policy. + // Spec Note: The value null for a violation’s resource is only allowed while the violation is being populated. + // By the time the violation is reported and its resource is used for obtaining the blocked URI, the + // violation’s resource should be populated with a URL or one of the allowed strings. + ResourceType m_resource; + + // https://w3c.github.io/webappsec-csp/#violation-referrer + // Each violation has a referrer, which is either null, or a URL. It represents the referrer of the resource whose + // policy was violated. + Optional m_referrer; + + // https://w3c.github.io/webappsec-csp/#violation-policy + // Each violation has a policy, which is the policy that has been violated. + GC::Ref m_policy; + + // https://w3c.github.io/webappsec-csp/#violation-effective-directive + // Each violation has an effective directive which is a non-empty string representing the directive whose enforcement + // caused the violation. + String m_effective_directive; + + // https://w3c.github.io/webappsec-csp/#violation-source-file + // Each violation has a source file, which is either null or a URL. + Optional m_source_file; + + // https://w3c.github.io/webappsec-csp/#violation-line-number + // Each violation has a line number, which is a non-negative integer. + u32 m_line_number { 0 }; + + // https://w3c.github.io/webappsec-csp/#violation-column-number + // Each violation has a column number, which is a non-negative integer. + u32 m_column_number { 0 }; + + // https://w3c.github.io/webappsec-csp/#violation-element + // Each violation has a element, which is either null or an element. + GC::Ptr m_element; + + // https://w3c.github.io/webappsec-csp/#violation-sample + // Each violation has a sample, which is a string. It is the empty string unless otherwise specified. + String m_sample; +}; + +} diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index f93dc380a7d..a0b41e4c5af 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -103,6 +103,7 @@ namespace Web::ContentSecurityPolicy { class Policy; class PolicyList; class SecurityPolicyViolationEvent; +class Violation; struct SecurityPolicyViolationEventInit; struct SerializedPolicy; } diff --git a/Libraries/LibWeb/Infra/JSON.cpp b/Libraries/LibWeb/Infra/JSON.cpp index ddd2c67e71c..eb453cfd3f6 100644 --- a/Libraries/LibWeb/Infra/JSON.cpp +++ b/Libraries/LibWeb/Infra/JSON.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -66,4 +67,108 @@ WebIDL::ExceptionOr serialize_javascript_value_to_json_bytes(JS::VM& return TRY_OR_THROW_OOM(vm, ByteBuffer::copy(string.bytes())); } +// https://infra.spec.whatwg.org/#convert-an-infra-value-to-a-json-compatible-javascript-value +[[nodiscard]] static JS::Value convert_an_infra_value_to_a_json_compatible_javascript_value(JS::Realm& realm, JSONTopLevel const& value) +{ + auto& vm = realm.vm(); + + if (value.has()) { + // 1. If value is a string, boolean, number, or null, then return value. + auto const& json_value = value.get(); + + if (json_value.has()) { + auto const& base_value = json_value.get(); + + if (base_value.has()) + return JS::PrimitiveString::create(vm, base_value.get()); + + if (base_value.has()) + return JS::Value(base_value.get()); + + if (base_value.has()) + return JS::Value(base_value.get()); + + if (base_value.has()) + return JS::Value(base_value.get()); + + VERIFY(base_value.has()); + return JS::js_null(); + } + + // 2. If value is a list, then: + VERIFY(json_value.has>()); + auto const& list_value = json_value.get>(); + + // 1. Let jsValue be ! ArrayCreate(0). + auto js_value = MUST(JS::Array::create(realm, 0)); + + // 2. Let i be 0. + u64 index = 0; + + // 3. For each listItem of value: + for (auto const& list_item : list_value) { + // 1. Let listItemJSValue be the result of converting an Infra value to a JSON-compatible JavaScript value, + // given listItem. + auto list_item_js_value = convert_an_infra_value_to_a_json_compatible_javascript_value(realm, JSONValue { list_item }); + + // 2. Perform ! CreateDataPropertyOrThrow(jsValue, ! ToString(i), listItemJSValue). + MUST(js_value->create_data_property_or_throw(index, list_item_js_value)); + + // 3. Set i to i + 1. + ++index; + } + + // 4. Return jsValue. + return js_value; + } + + // 3. Assert: value is a map. + VERIFY(value.has()); + auto const& map_value = value.get(); + + // 4. Let jsValue be ! OrdinaryObjectCreate(null). + auto js_value = JS::Object::create(realm, nullptr); + + // 5. For each mapKey → mapValue of value: + for (auto const& map_entry : map_value.value) { + // 1. Assert: mapKey is a string. + // 2. Let mapValueJSValue be the result of converting an Infra value to a JSON-compatible JavaScript value, + // given mapValue. + auto map_value_js_value = convert_an_infra_value_to_a_json_compatible_javascript_value(realm, map_entry.value); + + // 3. Perform ! CreateDataPropertyOrThrow(jsValue, mapKey, mapValueJSValue). + MUST(js_value->create_data_property_or_throw(map_entry.key.to_byte_string(), map_value_js_value)); + } + + // 6. Return jsValue. + return js_value; +} + +// https://infra.spec.whatwg.org/#serialize-an-infra-value-to-a-json-string +String serialize_an_infra_value_to_a_json_string(JS::Realm& realm, JSONTopLevel const& value) +{ + auto& vm = realm.vm(); + + // 1. Let jsValue be the result of converting an Infra value to a JSON-compatible JavaScript value, given value. + auto js_value = convert_an_infra_value_to_a_json_compatible_javascript_value(realm, value); + + // 2. Return ! Call(%JSON.stringify%, undefined, « jsValue »). + // Spec Note: Since no additional arguments are passed to %JSON.stringify%, the resulting string will have no + // whitespace inserted. + auto result = MUST(JS::call(vm, *realm.intrinsics().json_stringify_function(), JS::js_undefined(), js_value)); + VERIFY(result.is_string()); + return result.as_string().utf8_string(); +} + +// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-json-bytes +ByteBuffer serialize_an_infra_value_to_json_bytes(JS::Realm& realm, JSONTopLevel const& value) +{ + // 1. Let string be the result of serializing an Infra value to a JSON string, given value. + auto string = serialize_an_infra_value_to_a_json_string(realm, value); + + // 2. Return the result of running UTF-8 encode on string. [ENCODING] + // NOTE: LibJS strings are stored as UTF-8. + return MUST(ByteBuffer::copy(string.bytes())); +} + } diff --git a/Libraries/LibWeb/Infra/JSON.h b/Libraries/LibWeb/Infra/JSON.h index e359cd4180d..6eeddafb930 100644 --- a/Libraries/LibWeb/Infra/JSON.h +++ b/Libraries/LibWeb/Infra/JSON.h @@ -12,9 +12,18 @@ namespace Web::Infra { +using JSONBaseValue = Variant; +using JSONValue = Variant>; +struct JSONObject { + OrderedHashMap> value; +}; +using JSONTopLevel = Variant; + WebIDL::ExceptionOr parse_json_string_to_javascript_value(JS::Realm&, StringView); WebIDL::ExceptionOr parse_json_bytes_to_javascript_value(JS::Realm&, ReadonlyBytes); WebIDL::ExceptionOr serialize_javascript_value_to_json_string(JS::VM&, JS::Value); WebIDL::ExceptionOr serialize_javascript_value_to_json_bytes(JS::VM&, JS::Value); +String serialize_an_infra_value_to_a_json_string(JS::Realm&, JSONTopLevel const&); +ByteBuffer serialize_an_infra_value_to_json_bytes(JS::Realm&, JSONTopLevel const&); } From 6f771f45e2dfac7e63ad773bff3677fc9b2a459c Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Mon, 25 Nov 2024 17:29:27 +0000 Subject: [PATCH 045/141] LibWeb: Enforce Content Security Policy on Fetch requests --- Libraries/LibWeb/CMakeLists.txt | 1 + .../BlockingAlgorithms.cpp | 101 ++++++++++++++++++ .../BlockingAlgorithms.h | 15 +++ Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 4 +- .../Fetch/Infrastructure/HTTP/Requests.cpp | 3 + 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp create mode 100644 Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 07d1daea425..4e0a70eac0f 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -41,6 +41,7 @@ set(SOURCES ContentSecurityPolicy/Directives/DirectiveOperations.cpp ContentSecurityPolicy/Directives/Names.cpp ContentSecurityPolicy/Directives/SerializedDirective.cpp + ContentSecurityPolicy/BlockingAlgorithms.cpp ContentSecurityPolicy/Policy.cpp ContentSecurityPolicy/PolicyList.cpp ContentSecurityPolicy/SecurityPolicyViolationEvent.cpp diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp new file mode 100644 index 00000000000..c3af4811408 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::ContentSecurityPolicy { + +// https://w3c.github.io/webappsec-csp/#does-resource-hint-violate-policy +[[nodiscard]] static GC::Ptr does_resource_hint_request_violate_policy(JS::Realm& realm, GC::Ref request, GC::Ref policy) +{ + // 1. Let defaultDirective be policy’s first directive whose name is "default-src". + auto default_directive_iterator = policy->directives().find_if([](auto const& directive) { + return directive->name() == Directives::Names::DefaultSrc; + }); + + // 2. If defaultDirective does not exist, return "Does Not Violate". + if (default_directive_iterator.is_end()) + return {}; + + // 3. For each directive of policy: + for (auto directive : policy->directives()) { + // 1. Let result be the result of executing directive’s pre-request check on request and policy. + auto result = directive->pre_request_check(realm, request, policy); + + // 2. If result is "Allowed", then return "Does Not Violate". + if (result == Directives::Directive::Result::Allowed) { + return {}; + } + } + + // 4. Return defaultDirective. + return *default_directive_iterator; +} + +// https://w3c.github.io/webappsec-csp/#does-request-violate-policy +[[nodiscard]] static GC::Ptr does_request_violate_policy(JS::Realm& realm, GC::Ref request, GC::Ref policy) +{ + // 1. If request’s initiator is "prefetch", then return the result of executing § 6.7.2.2 Does resource hint + // request violate policy? on request and policy. + if (request->initiator() == Fetch::Infrastructure::Request::Initiator::Prefetch) + return does_resource_hint_request_violate_policy(realm, request, policy); + + // 2. Let violates be "Does Not Violate". + GC::Ptr violates; + + // 3. For each directive of policy: + for (auto directive : policy->directives()) { + // 1. Let result be the result of executing directive’s pre-request check on request and policy. + auto result = directive->pre_request_check(realm, request, policy); + + // 2. If result is "Blocked", then let violates be directive. + if (result == Directives::Directive::Result::Blocked) { + violates = directive; + } + } + + // 4. Return violates. + return violates; +} + +Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref request) +{ + // 1. Let CSP list be request’s policy container's CSP list. + auto csp_list = request->policy_container().get>()->csp_list; + + // 2. Let result be "Allowed". + auto result = Directives::Directive::Result::Allowed; + + // 3. For each policy of CSP list: + for (auto policy : csp_list->policies()) { + // 1. If policy’s disposition is "report", then skip to the next policy. + if (policy->disposition() == Policy::Disposition::Report) + continue; + + // 2. Let violates be the result of executing § 6.7.2.1 Does request violate policy? on request and policy. + auto violates = does_request_violate_policy(realm, request, policy); + + // 3. If violates is not "Does Not Violate", then: + if (violates) { + // 1. Execute § 5.5 Report a violation on the result of executing § 2.4.2 Create a violation object for + // request, and policy. on request, and policy. + auto violation = Violation::create_a_violation_object_for_request_and_policy(realm, request, policy); + violation->report_a_violation(realm); + + // 2. Set result to "Blocked". + result = Directives::Directive::Result::Blocked; + } + } + + // 4. Return result. + return result; +} + +} diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h new file mode 100644 index 00000000000..572d0e8e630 --- /dev/null +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::ContentSecurityPolicy { + +[[nodiscard]] Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref); + +} diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 4d382b74974..47663360a4f 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -295,8 +296,7 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra // should request be blocked by Content Security Policy returns blocked, then set response to a network error. if (Infrastructure::block_bad_port(request) == Infrastructure::RequestOrResponseBlocking::Blocked || MixedContent::should_fetching_request_be_blocked_as_mixed_content(request) == Infrastructure::RequestOrResponseBlocking::Blocked - || false // FIXME: "should request be blocked by Content Security Policy returns blocked" - ) { + || ContentSecurityPolicy::should_request_be_blocked_by_content_security_policy(realm, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) { response = Infrastructure::Response::network_error(vm, "Request was blocked"sv); } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp index c1fb681660b..4febb443950 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include From 51796e2d3a7da4290a21ecf244a1cc398dbb194e Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Thu, 28 Nov 2024 11:57:04 +0000 Subject: [PATCH 046/141] LibWeb: Report CSP violations for request --- .../BlockingAlgorithms.cpp | 24 +++++++++++++++++++ .../BlockingAlgorithms.h | 1 + Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 4 +++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp index c3af4811408..6b8bf4cd852 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp @@ -65,6 +65,30 @@ namespace Web::ContentSecurityPolicy { return violates; } +// https://w3c.github.io/webappsec-csp/#report-for-request +void report_content_security_policy_violations_for_request(JS::Realm& realm, GC::Ref request) +{ + // 1. Let CSP list be request’s policy container's CSP list. + auto csp_list = request->policy_container().get>()->csp_list; + + // 2. For each policy of CSP list: + for (auto policy : csp_list->policies()) { + // 1. If policy’s disposition is "enforce", then skip to the next policy. + if (policy->disposition() == Policy::Disposition::Enforce) + continue; + + // 2. Let violates be the result of executing § 6.7.2.1 Does request violate policy? on request and policy. + auto violates = does_request_violate_policy(realm, request, policy); + + // 3. If violates is not "Does Not Violate", then execute § 5.5 Report a violation on the result of executing + // § 2.4.2 Create a violation object for request, and policy. on request, and policy. + if (violates) { + auto violation = Violation::create_a_violation_object_for_request_and_policy(realm, request, policy); + violation->report_a_violation(realm); + } + } +} + Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref request) { // 1. Let CSP list be request’s policy container's CSP list. diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h index 572d0e8e630..019526fd837 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h @@ -10,6 +10,7 @@ namespace Web::ContentSecurityPolicy { +void report_content_security_policy_violations_for_request(JS::Realm&, GC::Ref); [[nodiscard]] Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref); } diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 47663360a4f..66a8d0dc2cc 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -286,7 +286,9 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra if (request->local_urls_only() && !Infrastructure::is_local_url(request->current_url())) response = Infrastructure::Response::network_error(vm, "Request with 'local-URLs-only' flag must have a local URL"sv); - // FIXME: 4. Run report Content Security Policy violations for request. + // 4. Run report Content Security Policy violations for request. + ContentSecurityPolicy::report_content_security_policy_violations_for_request(realm, request); + // FIXME: 5. Upgrade request to a potentially trustworthy URL, if appropriate. // 6. Upgrade a mixed content request to a potentially trustworthy URL, if appropriate. From 7643a079c08ab41b4449952f779d04e2adb78f9e Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Thu, 28 Nov 2024 12:30:36 +0000 Subject: [PATCH 047/141] LibWeb: Enforce Content Security Policy of Fetch responses --- .../BlockingAlgorithms.cpp | 35 +++++++++++++++++++ .../BlockingAlgorithms.h | 1 + Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 4 +-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp index 6b8bf4cd852..6d0f63be370 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp @@ -89,6 +89,7 @@ void report_content_security_policy_violations_for_request(JS::Realm& realm, GC: } } +// https://w3c.github.io/webappsec-csp/#should-block-request Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref request) { // 1. Let CSP list be request’s policy container's CSP list. @@ -122,4 +123,38 @@ Directives::Directive::Result should_request_be_blocked_by_content_security_poli return result; } +// https://w3c.github.io/webappsec-csp/#should-block-response +Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref response, GC::Ref request) +{ + // 1. Let CSP list be request’s policy container's CSP list. + auto csp_list = request->policy_container().get>()->csp_list; + + // 2. Let result be "Allowed". + auto result = Directives::Directive::Result::Allowed; + + // 3. For each policy of CSP list: + // Spec Note: This portion of the check verifies that the page can load the response. That is, that a Service + // Worker hasn't substituted a file which would violate the page’s CSP. + for (auto policy : csp_list->policies()) { + // 1. For each directive of policy: + for (auto directive : policy->directives()) { + // 1. If the result of executing directive’s post-request check is "Blocked", then: + if (directive->post_request_check(realm, request, response, policy) == Directives::Directive::Result::Blocked) { + // 1. Execute § 5.5 Report a violation on the result of executing § 2.4.2 Create a violation object for + // request, and policy. on request, and policy. + auto violation = Violation::create_a_violation_object_for_request_and_policy(realm, request, policy); + violation->report_a_violation(realm); + + // 2. If policy’s disposition is "enforce", then set result to "Blocked". + if (policy->disposition() == Policy::Disposition::Enforce) { + result = Directives::Directive::Result::Blocked; + } + } + } + } + + // 4. Return result. + return result; +} + } diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h index 019526fd837..6272c58f0a1 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h @@ -12,5 +12,6 @@ namespace Web::ContentSecurityPolicy { void report_content_security_policy_violations_for_request(JS::Realm&, GC::Ref); [[nodiscard]] Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref); +[[nodiscard]] Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref, GC::Ref); } diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 66a8d0dc2cc..bdf51051c2d 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -528,8 +528,8 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra if (!response->is_network_error() && ( // - should internalResponse to request be blocked as mixed content MixedContent::should_response_to_request_be_blocked_as_mixed_content(request, internal_response) == Infrastructure::RequestOrResponseBlocking::Blocked - // FIXME: - should internalResponse to request be blocked by Content Security Policy - || false + // - should internalResponse to request be blocked by Content Security Policy + || ContentSecurityPolicy::should_response_to_request_be_blocked_by_content_security_policy(realm, internal_response, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked // - should internalResponse to request be blocked due to its MIME type || Infrastructure::should_response_to_request_be_blocked_due_to_its_mime_type(internal_response, request) == Infrastructure::RequestOrResponseBlocking::Blocked // - should internalResponse to request be blocked due to nosniff From 83143e3018b19ebaf747a3c7bb9d6a914181b6d0 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 18 Mar 2025 15:00:44 +0000 Subject: [PATCH 048/141] LibWeb: Avoid repetition when serializing grid track placement values --- Libraries/LibWeb/CSS/GridTrackPlacement.cpp | 3 +-- .../css/css-grid/parsing/grid-area-computed.txt | 10 +++++----- .../css/css-grid/parsing/grid-area-shorthand.txt | 8 ++++---- .../css/css-grid/parsing/grid-area-valid.txt | 8 ++++---- .../css/css-grid/parsing/grid-column-shorthand.txt | 8 ++++---- .../css/css-grid/parsing/grid-row-shorthand.txt | 8 ++++---- 6 files changed, 22 insertions(+), 23 deletions(-) diff --git a/Libraries/LibWeb/CSS/GridTrackPlacement.cpp b/Libraries/LibWeb/CSS/GridTrackPlacement.cpp index 9aeab5ad14a..c5c4285f472 100644 --- a/Libraries/LibWeb/CSS/GridTrackPlacement.cpp +++ b/Libraries/LibWeb/CSS/GridTrackPlacement.cpp @@ -22,8 +22,7 @@ String GridTrackPlacement::to_string() const builder.appendff("{} {}", *area_or_line.line_number, *area_or_line.name); } else if (area_or_line.line_number.has_value()) { builder.appendff("{}", *area_or_line.line_number); - } - if (area_or_line.name.has_value()) { + } else if (area_or_line.name.has_value()) { builder.appendff("{}", *area_or_line.name); } }, diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt index 69b73336345..d6bb6d0ba8d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt @@ -2,8 +2,8 @@ Harness status: OK Found 30 tests -9 Pass -21 Fail +12 Pass +18 Fail Fail Property grid-area value 'auto / auto / auto / auto' Pass Property grid-row value 'auto / auto' Pass Property grid-column-end value 'auto' @@ -13,12 +13,12 @@ Fail Property grid-column-start value '-_π' Pass Property grid-row-end value '_9' Fail Property grid-area value '1 / 90 -a- / auto / auto' Fail Property grid-row value '2 az / auto' -Fail Property grid-column value '9 / -19 zA' +Pass Property grid-column value '9 / -19 zA' Pass Property grid-row-start value '-19' Fail Property grid-row-start value '9 -Z_' -Fail Property grid-column-start value '-44 Z' +Pass Property grid-column-start value '-44 Z' Fail Property grid-row-end value '1 -πA' -Fail Property grid-column-end value '5 π_' +Pass Property grid-column-end value '5 π_' Fail Property grid-area value 'span 2 i / auto / auto / auto' Pass Property grid-row value 'span 2 / auto' Fail Property grid-column-start value 'span 1 i' diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-shorthand.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-shorthand.txt index fa20a853e32..e956d974aed 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-shorthand.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-shorthand.txt @@ -2,8 +2,8 @@ Harness status: OK Found 53 tests -42 Pass -11 Fail +44 Pass +9 Fail Pass e.style['grid-area'] = "auto" should set grid-column-end Pass e.style['grid-area'] = "auto" should set grid-column-start Pass e.style['grid-area'] = "auto" should set grid-row-end @@ -45,8 +45,8 @@ Pass e.style['grid-row'] = "auto" should not set unrelated longhands Pass e.style['grid-row'] = "one / 2" should set grid-row-end Pass e.style['grid-row'] = "one / 2" should set grid-row-start Pass e.style['grid-row'] = "one / 2" should not set unrelated longhands -Fail e.style['grid-row'] = "1 two / four 3" should set grid-row-end -Fail e.style['grid-row'] = "1 two / four 3" should set grid-row-start +Pass e.style['grid-row'] = "1 two / four 3" should set grid-row-end +Pass e.style['grid-row'] = "1 two / four 3" should set grid-row-start Pass e.style['grid-row'] = "1 two / four 3" should not set unrelated longhands Pass e.style['grid-column'] = "5 span" should set grid-column-end Pass e.style['grid-column'] = "5 span" should set grid-column-start diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt index d37fda17e4f..78aecb39938 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt @@ -2,8 +2,8 @@ Harness status: OK Found 57 tests -13 Pass -44 Fail +15 Pass +42 Fail Fail e.style['grid-area'] = "auto" should set the property value Fail e.style['grid-area'] = "auto / auto" should set the property value Fail e.style['grid-area'] = "auto / auto / auto" should set the property value @@ -28,9 +28,9 @@ Fail e.style['grid-column'] = "-A0 33" should set the property value Pass e.style['grid-row-start'] = "-19" should set the property value Fail e.style['grid-row-start'] = "9 -Z_" should set the property value Pass e.style['grid-column-start'] = "+90" should set the property value -Fail e.style['grid-column-start'] = "Z -44" should set the property value +Pass e.style['grid-column-start'] = "Z -44" should set the property value Fail e.style['grid-row-end'] = "1 -πA" should set the property value -Fail e.style['grid-column-end'] = "π_ +5" should set the property value +Pass e.style['grid-column-end'] = "π_ +5" should set the property value Fail e.style['grid-area'] = "span 2 i" should set the property value Fail e.style['grid-area'] = "i 2 SpAn" should set the property value Fail e.style['grid-area'] = "span 1 i" should set the property value diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shorthand.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shorthand.txt index c4350c9f3d7..bd3e8990136 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shorthand.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shorthand.txt @@ -2,8 +2,8 @@ Harness status: OK Found 48 tests -36 Pass -12 Fail +38 Pass +10 Fail Pass e.style['grid-column'] = "auto / auto" should set grid-column-end Pass e.style['grid-column'] = "auto / auto" should set grid-column-start Pass e.style['grid-column'] = "auto / auto" should not set unrelated longhands @@ -29,10 +29,10 @@ Pass e.style['grid-column'] = "span 2" should set grid-column-end Pass e.style['grid-column'] = "span 2" should set grid-column-start Pass e.style['grid-column'] = "span 2" should not set unrelated longhands Pass e.style['grid-column'] = "3 last / auto" should set grid-column-end -Fail e.style['grid-column'] = "3 last / auto" should set grid-column-start +Pass e.style['grid-column'] = "3 last / auto" should set grid-column-start Pass e.style['grid-column'] = "3 last / auto" should not set unrelated longhands Fail e.style['grid-column'] = "3 last" should set grid-column-end -Fail e.style['grid-column'] = "3 last" should set grid-column-start +Pass e.style['grid-column'] = "3 last" should set grid-column-start Pass e.style['grid-column'] = "3 last" should not set unrelated longhands Fail e.style['grid-column'] = "span first / auto" should set grid-column-end Fail e.style['grid-column'] = "span first / auto" should set grid-column-start diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shorthand.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shorthand.txt index 788ae97a52c..5ba3558492d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shorthand.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shorthand.txt @@ -2,8 +2,8 @@ Harness status: OK Found 48 tests -36 Pass -12 Fail +38 Pass +10 Fail Pass e.style['grid-row'] = "auto / auto" should set grid-row-end Pass e.style['grid-row'] = "auto / auto" should set grid-row-start Pass e.style['grid-row'] = "auto / auto" should not set unrelated longhands @@ -29,10 +29,10 @@ Pass e.style['grid-row'] = "span 2" should set grid-row-end Pass e.style['grid-row'] = "span 2" should set grid-row-start Pass e.style['grid-row'] = "span 2" should not set unrelated longhands Pass e.style['grid-row'] = "3 last / auto" should set grid-row-end -Fail e.style['grid-row'] = "3 last / auto" should set grid-row-start +Pass e.style['grid-row'] = "3 last / auto" should set grid-row-start Pass e.style['grid-row'] = "3 last / auto" should not set unrelated longhands Fail e.style['grid-row'] = "3 last" should set grid-row-end -Fail e.style['grid-row'] = "3 last" should set grid-row-start +Pass e.style['grid-row'] = "3 last" should set grid-row-start Pass e.style['grid-row'] = "3 last" should not set unrelated longhands Fail e.style['grid-row'] = "span first / auto" should set grid-row-end Fail e.style['grid-row'] = "span first / auto" should set grid-row-start From 6811264b111545608618136a3132af4ea27a38d8 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 18 Mar 2025 15:03:35 +0000 Subject: [PATCH 049/141] LibWeb: Ensure shortest serialization is used for `grid-row` values --- .../LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp | 2 +- .../css/css-grid/parsing/grid-area-computed.txt | 6 +++--- .../css/css-grid/parsing/grid-area-valid.txt | 10 +++++----- .../parsing/grid-row-shortest-serialization.txt | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index 90363c03366..0640f1e2704 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -337,7 +337,7 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const case PropertyID::GridRow: { auto start = longhand(PropertyID::GridRowStart); auto end = longhand(PropertyID::GridRowEnd); - if (end->as_grid_track_placement().grid_track_placement().is_auto()) + if (end->as_grid_track_placement().grid_track_placement().is_auto() || start == end) return start->to_string(mode); return MUST(String::formatted("{} / {}", start->to_string(mode), end->to_string(mode))); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt index d6bb6d0ba8d..41438e56586 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-computed.txt @@ -2,8 +2,8 @@ Harness status: OK Found 30 tests -12 Pass -18 Fail +13 Pass +17 Fail Fail Property grid-area value 'auto / auto / auto / auto' Pass Property grid-row value 'auto / auto' Pass Property grid-column-end value 'auto' @@ -12,7 +12,7 @@ Pass Property grid-row-start value 'AZ' Fail Property grid-column-start value '-_π' Pass Property grid-row-end value '_9' Fail Property grid-area value '1 / 90 -a- / auto / auto' -Fail Property grid-row value '2 az / auto' +Pass Property grid-row value '2 az / auto' Pass Property grid-column value '9 / -19 zA' Pass Property grid-row-start value '-19' Fail Property grid-row-start value '9 -Z_' diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt index 78aecb39938..997e8a8f1c6 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt @@ -2,8 +2,8 @@ Harness status: OK Found 57 tests -15 Pass -42 Fail +18 Pass +39 Fail Fail e.style['grid-area'] = "auto" should set the property value Fail e.style['grid-area'] = "auto / auto" should set the property value Fail e.style['grid-area'] = "auto / auto / auto" should set the property value @@ -15,13 +15,13 @@ Pass e.style['grid-column-end'] = "AuTo" should set the property value Fail e.style['grid-area'] = "--a" should set the property value Fail e.style['grid-row'] = "-zπ" should set the property value Fail e.style['grid-row'] = "-zπ/-zπ" should set the property value -Fail e.style['grid-row'] = "i / i" should set the property value +Pass e.style['grid-row'] = "i / i" should set the property value Pass e.style['grid-row-start'] = "AZ" should set the property value Fail e.style['grid-column-start'] = "-_π" should set the property value Pass e.style['grid-row-end'] = "_9" should set the property value Fail e.style['grid-area'] = "1" should set the property value Fail e.style['grid-area'] = "+90 -a-" should set the property value -Fail e.style['grid-row'] = "az 2" should set the property value +Pass e.style['grid-row'] = "az 2" should set the property value Pass e.style['grid-column'] = "9" should set the property value Fail e.style['grid-column'] = "-19 zA" should set the property value Fail e.style['grid-column'] = "-A0 33" should set the property value @@ -55,7 +55,7 @@ Fail e.style['grid-area'] = "auto / i / 2 j" should set the property value Fail e.style['grid-area'] = "auto / i / 2 j / span 3 k" should set the property value Pass e.style['grid-row'] = "auto / i" should set the property value Fail e.style['grid-row'] = "i / auto" should set the property value -Fail e.style['grid-row'] = "2 i / auto" should set the property value +Pass e.style['grid-row'] = "2 i / auto" should set the property value Pass e.style['grid-row'] = "1 / auto" should set the property value Fail e.style['grid-column'] = "2 j / span 3 k" should set the property value Fail e.style['grid-column-end'] = "\\31st" should set the property value diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shortest-serialization.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shortest-serialization.txt index 068179b6c19..e78a09c8569 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shortest-serialization.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-row-shortest-serialization.txt @@ -2,8 +2,8 @@ Harness status: OK Found 15 tests -8 Pass -7 Fail +11 Pass +4 Fail Pass Property grid-row value 'auto / auto' Pass Property grid-row value 'auto' Pass Property grid-row value '10 / auto' @@ -12,10 +12,10 @@ Pass Property grid-row value '-10 / auto' Pass Property grid-row value '-10' Pass Property grid-row value 'span 2 / auto' Pass Property grid-row value 'span 2' -Fail Property grid-row value '3 last / auto' -Fail Property grid-row value '3 last' +Pass Property grid-row value '3 last / auto' +Pass Property grid-row value '3 last' Fail Property grid-row value 'span first / auto' Fail Property grid-row value 'span first' Fail Property grid-row value 'span 2 first / auto' Fail Property grid-row value 'span 2 first' -Fail Property grid-row value 'last / last' \ No newline at end of file +Pass Property grid-row value 'last / last' \ No newline at end of file From f0917ea150fa1e75dd6910f8ec41e32b508942ea Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 18 Mar 2025 15:08:05 +0000 Subject: [PATCH 050/141] LibWeb: Ensure shortest serialization is used for `grid-column` values --- .../LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp | 2 +- .../css/css-grid/parsing/grid-area-valid.txt | 6 +++--- .../parsing/grid-column-shortest-serialization.txt | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index 0640f1e2704..7404336b8c4 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -330,7 +330,7 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const case PropertyID::GridColumn: { auto start = longhand(PropertyID::GridColumnStart); auto end = longhand(PropertyID::GridColumnEnd); - if (end->as_grid_track_placement().grid_track_placement().is_auto()) + if (end->as_grid_track_placement().grid_track_placement().is_auto() || start == end) return start->to_string(mode); return MUST(String::formatted("{} / {}", start->to_string(mode), end->to_string(mode))); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt index 997e8a8f1c6..ef2b5ae2a44 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-area-valid.txt @@ -2,8 +2,8 @@ Harness status: OK Found 57 tests -18 Pass -39 Fail +19 Pass +38 Fail Fail e.style['grid-area'] = "auto" should set the property value Fail e.style['grid-area'] = "auto / auto" should set the property value Fail e.style['grid-area'] = "auto / auto / auto" should set the property value @@ -23,7 +23,7 @@ Fail e.style['grid-area'] = "1" should set the property value Fail e.style['grid-area'] = "+90 -a-" should set the property value Pass e.style['grid-row'] = "az 2" should set the property value Pass e.style['grid-column'] = "9" should set the property value -Fail e.style['grid-column'] = "-19 zA" should set the property value +Pass e.style['grid-column'] = "-19 zA" should set the property value Fail e.style['grid-column'] = "-A0 33" should set the property value Pass e.style['grid-row-start'] = "-19" should set the property value Fail e.style['grid-row-start'] = "9 -Z_" should set the property value diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shortest-serialization.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shortest-serialization.txt index 6f53df75fcf..995e97f5880 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shortest-serialization.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/parsing/grid-column-shortest-serialization.txt @@ -2,20 +2,20 @@ Harness status: OK Found 16 tests -8 Pass -8 Fail +12 Pass +4 Fail Pass Property grid-column value 'auto / auto' Pass Property grid-column value 'auto' Pass Property grid-column value '10 / auto' Pass Property grid-column value '10' Pass Property grid-column value '-10 / auto' Pass Property grid-column value '-10' -Fail Property grid-column value 'first / first' -Fail Property grid-column value 'first' +Pass Property grid-column value 'first / first' +Pass Property grid-column value 'first' Pass Property grid-column value 'span 2 / auto' Pass Property grid-column value 'span 2' -Fail Property grid-column value '2 first / auto' -Fail Property grid-column value '2 first' +Pass Property grid-column value '2 first / auto' +Pass Property grid-column value '2 first' Fail Property grid-column value 'span first / auto' Fail Property grid-column value 'span first' Fail Property grid-column value 'span 2 first / auto' From b8fa355a21344ba0bcbbd96923d3da571a91cab0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 18 Mar 2025 21:44:24 +0100 Subject: [PATCH 051/141] LibWeb: Implement "transferred size suggestion" part of GFC spec --- .../LibWeb/Layout/GridFormattingContext.cpp | 33 ++++++++++++++-- .../LibWeb/Layout/GridFormattingContext.h | 1 + .../ref-filled-green-100px-square-image.html | 8 ++++ .../grid-items/support/100x100-green.png | Bin 0 -> 91 bytes .../grid-minimum-size-grid-items-008.html | 34 ++++++++++++++++ .../grid-minimum-size-grid-items-009.html | 37 ++++++++++++++++++ .../grid-items/support/200x200-green.png | Bin 0 -> 120451 bytes .../grid-items/support/50x25-green.png | Bin 0 -> 100 bytes 8 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/ref-filled-green-100px-square-image.html create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/support/100x100-green.png create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/grid-minimum-size-grid-items-008.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/grid-minimum-size-grid-items-009.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/support/200x200-green.png create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/support/50x25-green.png diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 30bd5fd16fd..bec973d9521 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -2548,16 +2548,41 @@ Optional GridFormattingContext::specified_size_suggestion(GridItem co return {}; } +Optional GridFormattingContext::transferred_size_suggestion(GridItem const& item, GridDimension dimension) const +{ + // https://www.w3.org/TR/css-grid-2/#transferred-size-suggestion + // If the item has a preferred aspect ratio and its preferred size in the opposite axis is definite, then the transferred + // size suggestion is that size (clamped by the opposite-axis minimum and maximum sizes if they are definite), converted + // through the aspect ratio. It is otherwise undefined. + if (!item.box->preferred_aspect_ratio().has_value()) { + return {}; + } + + CSS::Size const& preferred_size_in_opposite_axis = get_item_preferred_size(item, dimension == GridDimension::Column ? GridDimension::Row : GridDimension::Column); + if (preferred_size_in_opposite_axis.is_length()) { + auto opposite_axis_size = preferred_size_in_opposite_axis.length().to_px(item.box); + // FIXME: Clamp by opposite-axis minimum and maximum sizes if they are definite + return opposite_axis_size * item.box->preferred_aspect_ratio().value(); + } + + return {}; +} + CSSPixels GridFormattingContext::content_based_minimum_size(GridItem const& item, GridDimension const dimension) const { // https://www.w3.org/TR/css-grid-1/#content-based-minimum-size - // The content-based minimum size for a grid item in a given dimension is its specified size suggestion if it exists, - // otherwise its transferred size suggestion if that exists, - // else its content size suggestion, see below. + CSSPixels result = 0; + // The content-based minimum size for a grid item in a given dimension is its specified size suggestion if it exists, if (auto specified_size_suggestion = this->specified_size_suggestion(item, dimension); specified_size_suggestion.has_value()) { result = specified_size_suggestion.value(); - } else { + } + // otherwise its transferred size suggestion if that exists, + else if (auto transferred_size_suggestion = this->transferred_size_suggestion(item, dimension); transferred_size_suggestion.has_value()) { + result = transferred_size_suggestion.value(); + } + // else its content size suggestion. + else { result = content_size_suggestion(item, dimension); } diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.h b/Libraries/LibWeb/Layout/GridFormattingContext.h index 9da3fe7169a..f13078f8f0f 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -336,6 +336,7 @@ private: CSSPixels content_size_suggestion(GridItem const&, GridDimension const) const; Optional specified_size_suggestion(GridItem const&, GridDimension const) const; + Optional transferred_size_suggestion(GridItem const&, GridDimension const) const; CSSPixels content_based_minimum_size(GridItem const&, GridDimension const) const; CSSPixels automatic_minimum_size(GridItem const&, GridDimension const) const; CSSPixels calculate_minimum_contribution(GridItem const&, GridDimension const) const; diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/ref-filled-green-100px-square-image.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/ref-filled-green-100px-square-image.html new file mode 100644 index 00000000000..5906160218c --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/ref-filled-green-100px-square-image.html @@ -0,0 +1,8 @@ + + +CSS Reftest Reference: 100x100 green square image + + +

Test passes if there is a filled green square and no red.

+ +Image download support must be enabled diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/support/100x100-green.png b/Tests/LibWeb/Ref/expected/wpt-import/css/css-grid/grid-items/support/100x100-green.png new file mode 100644 index 0000000000000000000000000000000000000000..25b76c3c6f216793a36b1f29287dafd993898c67 GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0y~yU`PRBMrH;E1}`0UaRvqk<^Z1%SB3_LIX{<9WME(r s_H=O!sbEZApe@0=I6 + +CSS Grid Layout Test: Minimum size of grid items + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+ Image download support must be enabled +
+ diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/grid-minimum-size-grid-items-009.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/grid-minimum-size-grid-items-009.html new file mode 100644 index 00000000000..9fe7e98368c --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/grid-minimum-size-grid-items-009.html @@ -0,0 +1,37 @@ + + +CSS Grid Layout Test: Minimum size of grid items + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+ Image download support must be enabled + Image download support must be enabled +
+ diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/support/200x200-green.png b/Tests/LibWeb/Ref/input/wpt-import/css/css-grid/grid-items/support/200x200-green.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b591f05b5e052e562443050898abc734c9b153 GIT binary patch literal 120451 zcmeAS@N?(olHy`uVBq!ia0y~yU^oH7983%h3`$m(Cm9$N7(87ZLn;^<^(i_zc9W`(?1V$6qXd0kN2#h8yngr{pp`!_FG!4)s1V$4UO@ejQ(9wi7 zng(bR0;36wCc!#t=xD+kO#?IufzgCTlVBY+bTna&rU9CSz-YpvNwAI@I-0OX(*R9E zU^HRTBv|npx>-hr6}A=tufpLXIhx3ZOH2+je~c!qK^7sy#W|x1Yq-SZAoIs)!Wv`| zGF+T9ny`jTOb#-Cj3%r>79qpMIim?{xWwcj^T%ky8e|bNT%0qSu!c)a4l;j?Caggg zA;ZNvqX}!c#N;6J$7sSDWDzo4oHLrRhD%HiGJg!tgr)gYeesA|3oyu11(lsMI1?F_ zJvnL|{X<|hVbMR#M}&=|32Q_|GyOd|ny}~}<|D$!(S$W3qM81l98FmC5AzXW<7mPf z5z$P4PmU%m`iJ?5uyHhDjfiNbzb8i%7X8C~MA$f*utr2Q)8CV$35)(=J|b)!O;{r$ znu+(Ml4HLKwbufSCNko~e^ez2AuyV-NC>P^rNoE8Xu={stVUIm5CWqKi-f=$RZ4sa zj3zAN!)jC|2_Z0=ut*53QKiI(z-YoEKCDJnk`Mx;35$fl8dXYs2#h8y;=^iGB?%!g zny^R+tWl-JhrnpUB0j7}Rgw?_qX~EyJvN%K1~&YMt?i=;YuHBZ zz}?%QSi?Sw2gZY=32R`& zec0PNny`j_6c3CCM-$e-g!{0!bu?iO`zRh54~{0RfeH6vZ|i8n8a7es7_?n8CRgU~nZ0YP)7cB{FIU-l%!FLtr#v;SQTo845yRG+|K? zc%xczhrnpU!W}lFG8BZsXu_f(@J6-b4uR2xg*$9UWhe-N(S$`o;Eig<9Ri~X3wPLz z%1{sjqX~V89HV}xW(bTXENX`4s9Eq37)@C4pcrLPGXzEx z7Bxe2)GT-ij3z92P>eFD83LmTi<+T1Y8E^MMiUl1C`K9741v*vMa|G0H47dBqX`Qh z6r&7khQMgTqGo80ngtJm(S!vLictnNLtr#vQ8P40&4P!(Xu^UA#lSIK*NEf{=d}Q% ziEQA~*08pDG+_}+tZg153G3YKy9~p5Ex@o&V+=zikqz%i7>>RhO<2P*RtJHfMibT`h=k$jgwcdG z9AkA5_-Qm@4T4A*j!qa&Si><^2Z5hP6V@PzgyHCf(S$V|V|5VtX*6LCf=C#SP8dyC z!!cF|fuBYb)*y(4;pl|Xgf$#vbrASzG+_;bNEp;ksGJfbLE&0}(L^?=V|eH~ZZu&H z-MAanE+0);gF1SKuH!}%*3gZ+LGAL(2)k`GVGZHP8_Z50O<03Dc80LqMibT$ zj=aI_^wEShm}6%MyKOXK4dKWe%uXLoSc5rshOpacp0JpYwyzsq3owLJ2o;=8^F&4k zH;x)H1VdmnVGY4Z8wySyO;|%AmWH4kM-$c%jI^QP&0q7}OBxlVuXHd~%>4B Date: Tue, 18 Mar 2025 00:13:20 +1300 Subject: [PATCH 052/141] LibJS: Fix UAF in ECMAScriptFunctionObject::internal_construct Currently, we create `this_argument` with `ordinary_create_from_constructor`, then we use `arguments_list` to build the callee_context. The issue is we don't properly model the side-effects of `ordinary_create_from_constructor`, if `new_target` is a proxy object then when we `get` the prototype, arbitrary javascript can run. This javascript could perform a function call with enough arguments to reallocate the interpreters m_argument_values_buffer vector. This is dangerous and leads to a use-after-free, as our stack frame maintains a pointer to m_argument_values_buffer (`arguments_list`). --- .../Runtime/ECMAScriptFunctionObject.cpp | 22 ++++++------- ...ied-constructor-leads-to-use-after-free.js | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 Libraries/LibJS/Tests/regress/proxied-constructor-leads-to-use-after-free.js diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index da4507f152c..ac61bfc35fa 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -439,6 +439,17 @@ ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct( { auto& vm = this->vm(); + auto callee_context = ExecutionContext::create(); + + // Non-standard + callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size())); + callee_context->arguments.append(arguments_list.data(), arguments_list.size()); + callee_context->passed_argument_count = arguments_list.size(); + if (arguments_list.size() < m_formal_parameters.size()) { + for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i) + callee_context->arguments.append(js_undefined()); + } + // 1. Let callerContext be the running execution context. // NOTE: No-op, kept by the VM in its execution context stack. @@ -453,17 +464,6 @@ ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct( this_argument = TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag)); } - auto callee_context = ExecutionContext::create(); - - // Non-standard - callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size())); - callee_context->arguments.append(arguments_list.data(), arguments_list.size()); - callee_context->passed_argument_count = arguments_list.size(); - if (arguments_list.size() < m_formal_parameters.size()) { - for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i) - callee_context->arguments.append(js_undefined()); - } - // 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget). // NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check. TRY(prepare_for_ordinary_call(*callee_context, &new_target)); diff --git a/Libraries/LibJS/Tests/regress/proxied-constructor-leads-to-use-after-free.js b/Libraries/LibJS/Tests/regress/proxied-constructor-leads-to-use-after-free.js new file mode 100644 index 00000000000..b7523d1dd4d --- /dev/null +++ b/Libraries/LibJS/Tests/regress/proxied-constructor-leads-to-use-after-free.js @@ -0,0 +1,33 @@ +test("Proxied constructor should handle argument_buffer reallocation during prototype get()", () => { + function foo() {} + + let handler = { + get() { + // prettier-ignore + foo( + // make extra sure we trigger a reallocation + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 + ); + + return null; + }, + }; + + function Construct() { + // later use dangling pointer + console.log(arguments); + } + + let ConstructProxy = new Proxy(Construct, handler); + + new ConstructProxy(0x1); +}); From 3ebdb64fedf37735699d6301b5405efaaed63378 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 15:57:43 +0000 Subject: [PATCH 053/141] LibWeb: Replace ::-webkit pseudo-elements with ones from CSS Forms spec This spec is very early on, and likely to change. However, it still feels preferable to use these rather than the prefixed -webkit ones. Plus, as we have a `::fill` on range inputs, we can use that for styling the bar instead of inserting CSS from C++. --- Libraries/LibWeb/CSS/Default.css | 109 ++++++++++-------- Libraries/LibWeb/CSS/Selector.cpp | 33 ++---- Libraries/LibWeb/CSS/Selector.h | 8 +- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 10 +- Libraries/LibWeb/HTML/HTMLMeterElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLProgressElement.cpp | 4 +- 6 files changed, 85 insertions(+), 81 deletions(-) diff --git a/Libraries/LibWeb/CSS/Default.css b/Libraries/LibWeb/CSS/Default.css index 62864d65f52..c29b243922d 100644 --- a/Libraries/LibWeb/CSS/Default.css +++ b/Libraries/LibWeb/CSS/Default.css @@ -78,26 +78,33 @@ input[type=range] { display: inline-block; width: 20ch; height: 16px; -} -input[type=range]::-webkit-slider-runnable-track, input[type=range]::-webkit-slider-thumb { - display: block; -} -input[type=range]::-webkit-slider-runnable-track { - position: relative; - height: 4px; - width: 100%; - margin-top: 6px; - background-color: AccentColorText; - border: 1px solid rgba(0, 0, 0, 0.5); -} -input[type=range]::-webkit-slider-thumb { - margin-top: -6px; - width: 16px; - height: 16px; - transform: translateX(-50%); - border-radius: 50%; - outline: 1px solid rgba(0, 0, 0, 0.5); - z-index: 1; + + &::track { + display: block; + position: relative; + height: 4px; + width: 100%; + margin-top: 6px; + background-color: AccentColorText; + border: 1px solid rgba(0, 0, 0, 0.5); + } + + &::fill { + display: block; + position: absolute; + height: 100%; + } + + &::thumb { + display: block; + margin-top: -6px; + width: 16px; + height: 16px; + transform: translateX(-50%); + border-radius: 50%; + outline: 1px solid rgba(0, 0, 0, 0.5); + z-index: 1; + } } /* Custom styles */ @@ -105,23 +112,31 @@ meter { display: inline-block; width: 300px; height: 12px; -} -meter::-webkit-meter-bar, meter::-webkit-meter-optimum-value, meter::-webkit-meter-suboptimum-value, meter::-webkit-meter-even-less-good-value { - display: block; - height: 100%; -} -meter::-webkit-meter-bar { - background-color: hsl(0, 0%, 96%); - border: 1px solid rgba(0, 0, 0, 0.5); -} -meter::-webkit-meter-optimum-value { - background-color: hsl(141, 53%, 53%); -} -meter::-webkit-meter-suboptimum-value { - background-color: hsl(48, 100%, 67%); -} -meter::-webkit-meter-even-less-good-value { - background-color: hsl(348, 100%, 61%); + + &::track { + display: block; + height: 100%; + background-color: hsl(0, 0%, 96%); + border: 1px solid rgba(0, 0, 0, 0.5); + } + + &::-webkit-meter-optimum-value { + display: block; + height: 100%; + background-color: hsl(141, 53%, 53%); + } + + &::-webkit-meter-suboptimum-value { + display: block; + height: 100%; + background-color: hsl(48, 100%, 67%); + } + + &::-webkit-meter-even-less-good-value { + display: block; + height: 100%; + background-color: hsl(348, 100%, 61%); + } } /* Custom styles */ @@ -129,14 +144,18 @@ progress { display: inline-block; width: 300px; height: 12px; -} -progress::-webkit-progress-bar, progress::-webkit-progress-value { - display: block; - height: 100%; -} -progress::-webkit-progress-bar { - background-color: AccentColorText; - border: 1px solid rgba(0, 0, 0, 0.5); + + &::track { + display: block; + height: 100%; + background-color: AccentColorText; + border: 1px solid rgba(0, 0, 0, 0.5); + } + + &::fill { + display: block; + height: 100%; + } } /* 15.3.1 Hidden elements diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index 85016c1289e..7075bb1e0d9 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -548,26 +548,22 @@ StringView Selector::PseudoElement::name(Selector::PseudoElement::Type pseudo_el 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::Track: + return "track"sv; + case Selector::PseudoElement::Type::Fill: + return "fill"sv; + case Selector::PseudoElement::Type::Thumb: + return "thumb"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::FileSelectorButton: @@ -575,7 +571,6 @@ StringView Selector::PseudoElement::name(Selector::PseudoElement::Type pseudo_el case Selector::PseudoElement::Type::DetailsContent: return "details-content"sv; case Selector::PseudoElement::Type::KnownPseudoElementCount: - break; case Selector::PseudoElement::Type::UnknownWebKit: VERIFY_NOT_REACHED(); } @@ -594,18 +589,18 @@ Optional Selector::PseudoElement::from_string(FlyString 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("track"sv)) { + return Selector::PseudoElement { Selector::PseudoElement::Type::Track }; + } else if (name.equals_ignoring_ascii_case("fill"sv)) { + return Selector::PseudoElement { Selector::PseudoElement::Type::Fill }; + } else if (name.equals_ignoring_ascii_case("thumb"sv)) { + return Selector::PseudoElement { Selector::PseudoElement::Type::Thumb }; } 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)) { @@ -616,10 +611,6 @@ Optional Selector::PseudoElement::from_string(FlyString return Selector::PseudoElement { Selector::PseudoElement::Type::FileSelectorButton }; } else if (name.equals_ignoring_ascii_case("details-content"sv)) { return Selector::PseudoElement { Selector::PseudoElement::Type::DetailsContent }; - } 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 {}; } diff --git a/Libraries/LibWeb/CSS/Selector.h b/Libraries/LibWeb/CSS/Selector.h index f1d06669b48..35f3f257638 100644 --- a/Libraries/LibWeb/CSS/Selector.h +++ b/Libraries/LibWeb/CSS/Selector.h @@ -30,16 +30,14 @@ public: FirstLine, FirstLetter, Marker, - MeterBar, + Track, + Fill, + Thumb, MeterEvenLessGoodValue, MeterOptimumValue, MeterSuboptimumValue, - ProgressValue, - ProgressBar, Placeholder, Selection, - SliderRunnableTrack, - SliderThumb, Backdrop, FileSelectorButton, DetailsContent, diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 8e02be0eb50..cd64673a3b5 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -1183,19 +1183,15 @@ void HTMLInputElement::create_range_input_shadow_tree() set_shadow_root(shadow_root); m_slider_runnable_track = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); - m_slider_runnable_track->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::SliderRunnableTrack); + m_slider_runnable_track->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Track); MUST(shadow_root->append_child(*m_slider_runnable_track)); m_slider_progress_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); - MUST(m_slider_progress_element->set_attribute(HTML::AttributeNames::style, R"~~~( - display: block; - position: absolute; - height: 100%; - )~~~"_string)); + m_slider_progress_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Fill); MUST(m_slider_runnable_track->append_child(*m_slider_progress_element)); m_slider_thumb = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); - m_slider_thumb->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::SliderThumb); + m_slider_thumb->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Thumb); MUST(m_slider_runnable_track->append_child(*m_slider_thumb)); update_slider_shadow_tree_elements(); diff --git a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp index 4dbd4900f47..e5c1f25dfd2 100644 --- a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp @@ -197,7 +197,7 @@ void HTMLMeterElement::create_shadow_tree_if_needed() set_shadow_root(shadow_root); auto meter_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); - meter_bar_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterBar); + meter_bar_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Track); MUST(shadow_root->append_child(*meter_bar_element)); m_meter_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); diff --git a/Libraries/LibWeb/HTML/HTMLProgressElement.cpp b/Libraries/LibWeb/HTML/HTMLProgressElement.cpp index 6662d7c6502..1e106217604 100644 --- a/Libraries/LibWeb/HTML/HTMLProgressElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLProgressElement.cpp @@ -118,11 +118,11 @@ void HTMLProgressElement::create_shadow_tree_if_needed() set_shadow_root(shadow_root); auto progress_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); - progress_bar_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::ProgressBar); + progress_bar_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Track); MUST(shadow_root->append_child(*progress_bar_element)); m_progress_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); - m_progress_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::ProgressValue); + m_progress_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Fill); MUST(progress_bar_element->append_child(*m_progress_value_element)); update_progress_value_element(); } From 4c3c907041f4250e33d14b3974a7bc39db2a3b24 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 16:03:06 +0000 Subject: [PATCH 054/141] LibWeb: Implement progress/range-input accent color in CSS The `AccentColor` keyword does this in a simpler way, and allows authors to override it. --- Libraries/LibWeb/CSS/Default.css | 3 +++ Libraries/LibWeb/HTML/HTMLInputElement.cpp | 18 ------------------ Libraries/LibWeb/HTML/HTMLInputElement.h | 1 - Libraries/LibWeb/HTML/HTMLProgressElement.cpp | 12 ------------ Libraries/LibWeb/HTML/HTMLProgressElement.h | 1 - 5 files changed, 3 insertions(+), 32 deletions(-) diff --git a/Libraries/LibWeb/CSS/Default.css b/Libraries/LibWeb/CSS/Default.css index c29b243922d..0f378fccd55 100644 --- a/Libraries/LibWeb/CSS/Default.css +++ b/Libraries/LibWeb/CSS/Default.css @@ -93,6 +93,7 @@ input[type=range] { display: block; position: absolute; height: 100%; + background-color: AccentColor; } &::thumb { @@ -104,6 +105,7 @@ input[type=range] { border-radius: 50%; outline: 1px solid rgba(0, 0, 0, 0.5); z-index: 1; + background-color: AccentColor; } } @@ -155,6 +157,7 @@ progress { &::fill { display: block; height: 100%; + background-color: AccentColor; } } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index cd64673a3b5..7b47ae5e6ad 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -1276,24 +1276,6 @@ void HTMLInputElement::create_range_input_shadow_tree() add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), mousedown_callback)); } -void HTMLInputElement::computed_properties_changed() -{ - auto appearance = computed_properties()->appearance(); - if (appearance == CSS::Appearance::None) - return; - - auto accent_color = MUST(String::from_utf8(CSS::string_from_keyword(CSS::Keyword::Accentcolor))); - - auto const& accent_color_property = computed_properties()->property(CSS::PropertyID::AccentColor); - if (accent_color_property.has_color()) - accent_color = accent_color_property.to_string(CSS::CSSStyleValue::SerializationMode::Normal); - - if (m_slider_progress_element) - MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color)); - if (m_slider_thumb) - MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color)); -} - void HTMLInputElement::user_interaction_did_change_input_value() { // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index 7bcbc8f32a1..0dbab876d3d 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -268,7 +268,6 @@ private: // ^DOM::Element virtual i32 default_tab_index_value() const override; - virtual void computed_properties_changed() override; // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):dimension-attributes virtual bool supports_dimension_attributes() const override { return type_state() == TypeAttributeState::ImageButton; } diff --git a/Libraries/LibWeb/HTML/HTMLProgressElement.cpp b/Libraries/LibWeb/HTML/HTMLProgressElement.cpp index 1e106217604..87201df72b3 100644 --- a/Libraries/LibWeb/HTML/HTMLProgressElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLProgressElement.cpp @@ -133,16 +133,4 @@ void HTMLProgressElement::update_progress_value_element() MUST(m_progress_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position() * 100)))); } -void HTMLProgressElement::computed_properties_changed() -{ - auto accent_color = MUST(String::from_utf8(CSS::string_from_keyword(CSS::Keyword::Accentcolor))); - - auto const& accent_color_property = computed_properties()->property(CSS::PropertyID::AccentColor); - if (accent_color_property.has_color()) - accent_color = accent_color_property.to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); - - if (m_progress_value_element) - MUST(m_progress_value_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color)); -} - } diff --git a/Libraries/LibWeb/HTML/HTMLProgressElement.h b/Libraries/LibWeb/HTML/HTMLProgressElement.h index bf51bdbd880..e92527e417b 100644 --- a/Libraries/LibWeb/HTML/HTMLProgressElement.h +++ b/Libraries/LibWeb/HTML/HTMLProgressElement.h @@ -44,7 +44,6 @@ private: // ^DOM::Node virtual bool is_html_progress_element() const final { return true; } - virtual void computed_properties_changed() override; virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; From 1978578a7260a7c29ac9b55bb6d4bb2d47a5ccf3 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 16:55:00 +0000 Subject: [PATCH 055/141] LibWeb: Expose HTMLMeterElement's optimum/suboptimum/etc state No behaviour change, but this will allow us to switch over to pseudo-classes for this state. --- Libraries/LibWeb/HTML/HTMLMeterElement.cpp | 32 +++++++++++++++------- Libraries/LibWeb/HTML/HTMLMeterElement.h | 8 ++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp index e5c1f25dfd2..e901460d240 100644 --- a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp @@ -207,9 +207,6 @@ void HTMLMeterElement::create_shadow_tree_if_needed() void HTMLMeterElement::update_meter_value_element() { - if (!m_meter_value_element) - return; - // UA requirements for regions of the gauge: double value = this->value(); double min = this->min(); @@ -221,25 +218,40 @@ void HTMLMeterElement::update_meter_value_element() // If the optimum point is equal to the low boundary or the high boundary, or anywhere in between them, then the region between the low and high boundaries of the gauge must be treated as the optimum region, and the low and high parts, if any, must be treated as suboptimal. if (optimum >= low && optimum <= high) { if (value >= low && value <= high) - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterOptimumValue); + m_cached_value_state = ValueState::Optimal; else - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue); + m_cached_value_state = ValueState::Suboptimal; } // Otherwise, if the optimum point is less than the low boundary, then the region between the minimum value and the low boundary must be treated as the optimum region, the region from the low boundary up to the high boundary must be treated as a suboptimal region, and the remaining region must be treated as an even less good region. else if (optimum < low) { if (value >= low && value <= high) - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue); + m_cached_value_state = ValueState::Suboptimal; else - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterEvenLessGoodValue); + m_cached_value_state = ValueState::EvenLessGood; } // Finally, if the optimum point is higher than the high boundary, then the situation is reversed; the region between the high boundary and the maximum value must be treated as the optimum region, the region from the high boundary down to the low boundary must be treated as a suboptimal region, and the remaining region must be treated as an even less good region. else { if (value >= high && value <= max) - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterOptimumValue); + m_cached_value_state = ValueState::Optimal; else if (value >= low && value <= high) - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue); + m_cached_value_state = ValueState::Suboptimal; else - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterEvenLessGoodValue); + m_cached_value_state = ValueState::EvenLessGood; + } + + if (!m_meter_value_element) + return; + + switch (m_cached_value_state) { + case ValueState::Optimal: + m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterOptimumValue); + break; + case ValueState::Suboptimal: + m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue); + break; + case ValueState::EvenLessGood: + m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterEvenLessGoodValue); + break; } double position = (value - min) / (max - min) * 100; diff --git a/Libraries/LibWeb/HTML/HTMLMeterElement.h b/Libraries/LibWeb/HTML/HTMLMeterElement.h index 0a80948c61b..7dd2f8aaadb 100644 --- a/Libraries/LibWeb/HTML/HTMLMeterElement.h +++ b/Libraries/LibWeb/HTML/HTMLMeterElement.h @@ -45,6 +45,13 @@ public: // https://www.w3.org/TR/html-aria/#el-meter virtual Optional default_role() const override { return ARIA::Role::meter; } + enum class ValueState : u8 { + Optimal, + Suboptimal, + EvenLessGood, + }; + ValueState value_state() const { return m_cached_value_state; } + private: HTMLMeterElement(DOM::Document&, DOM::QualifiedName); @@ -56,6 +63,7 @@ private: void update_meter_value_element(); GC::Ptr m_meter_value_element; + ValueState m_cached_value_state { ValueState::Optimal }; }; } From d5b9c39a98acd0864abb18cbe4f584e38367b113 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 16:56:41 +0000 Subject: [PATCH 056/141] LibWeb: Replace webkit meter-state pseudo-elements with pseudo-classes This also implements the `:high-value` and `:low-value` that are in the spec. Same note as before about this being based on the very-drafty CSS Forms spec. In fact, some of this isn't even in that spec yet. Specifically, the `:suboptimal-value` and `:even-less-good-value` names are undecided and subject to change. However, it's clear that this is a pseudo-class situation, not a pseudo-element one, so I think this is still an improvement, as it allows styling of the `::fill` pseudo-element regardless of what state it is in. Relevant spec issue: https://github.com/openui/open-ui/issues/1130 --- Libraries/LibWeb/CSS/Default.css | 13 ++++++------- Libraries/LibWeb/CSS/PseudoClasses.json | 15 +++++++++++++++ Libraries/LibWeb/CSS/Selector.cpp | 12 ------------ Libraries/LibWeb/CSS/Selector.h | 3 --- Libraries/LibWeb/CSS/SelectorEngine.cpp | 22 ++++++++++++++++++++++ Libraries/LibWeb/HTML/HTMLMeterElement.cpp | 13 +------------ 6 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Libraries/LibWeb/CSS/Default.css b/Libraries/LibWeb/CSS/Default.css index 0f378fccd55..29645daa71d 100644 --- a/Libraries/LibWeb/CSS/Default.css +++ b/Libraries/LibWeb/CSS/Default.css @@ -122,21 +122,20 @@ meter { border: 1px solid rgba(0, 0, 0, 0.5); } - &::-webkit-meter-optimum-value { + &::fill { display: block; height: 100%; + } + + &:optimal-value::fill { background-color: hsl(141, 53%, 53%); } - &::-webkit-meter-suboptimum-value { - display: block; - height: 100%; + &:suboptimal-value::fill { background-color: hsl(48, 100%, 67%); } - &::-webkit-meter-even-less-good-value { - display: block; - height: 100%; + &:even-less-good-value::fill { background-color: hsl(348, 100%, 61%); } } diff --git a/Libraries/LibWeb/CSS/PseudoClasses.json b/Libraries/LibWeb/CSS/PseudoClasses.json index a799dba1c8b..b91702cc9e9 100644 --- a/Libraries/LibWeb/CSS/PseudoClasses.json +++ b/Libraries/LibWeb/CSS/PseudoClasses.json @@ -26,6 +26,9 @@ "enabled": { "argument": "" }, + "even-less-good-value": { + "argument": "" + }, "first-child": { "argument": "" }, @@ -44,6 +47,9 @@ "has": { "argument": "" }, + "high-value": { + "argument": "" + }, "host": { "argument": "?" }, @@ -74,6 +80,9 @@ "local-link": { "argument": "" }, + "low-value": { + "argument": "" + }, "modal": { "argument": "" }, @@ -104,6 +113,9 @@ "open": { "argument": "" }, + "optimal-value": { + "argument": "" + }, "popover-open": { "argument": "" }, @@ -134,6 +146,9 @@ "stalled": { "argument": "" }, + "suboptimal-value": { + "argument": "" + }, "target": { "argument": "" }, diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index 7075bb1e0d9..8f9333fd61f 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -554,12 +554,6 @@ StringView Selector::PseudoElement::name(Selector::PseudoElement::Type pseudo_el return "fill"sv; case Selector::PseudoElement::Type::Thumb: return "thumb"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::Placeholder: return "placeholder"sv; case Selector::PseudoElement::Type::Selection: @@ -595,12 +589,6 @@ Optional Selector::PseudoElement::from_string(FlyString return Selector::PseudoElement { Selector::PseudoElement::Type::Fill }; } else if (name.equals_ignoring_ascii_case("thumb"sv)) { return Selector::PseudoElement { Selector::PseudoElement::Type::Thumb }; - } 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("placeholder"sv)) { return Selector::PseudoElement { Selector::PseudoElement::Type::Placeholder }; } else if (name.equals_ignoring_ascii_case("selection"sv)) { diff --git a/Libraries/LibWeb/CSS/Selector.h b/Libraries/LibWeb/CSS/Selector.h index 35f3f257638..a670e402f45 100644 --- a/Libraries/LibWeb/CSS/Selector.h +++ b/Libraries/LibWeb/CSS/Selector.h @@ -33,9 +33,6 @@ public: Track, Fill, Thumb, - MeterEvenLessGoodValue, - MeterOptimumValue, - MeterSuboptimumValue, Placeholder, Selection, Backdrop, diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index ebffb46a715..b7ee412148e 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -423,6 +424,13 @@ static inline bool matches_host_pseudo_class(GC::Ref element return true; } +static bool matches_optimal_value_pseudo_class(DOM::Element const& element, HTML::HTMLMeterElement::ValueState desired_state) +{ + if (auto* meter = as_if(element)) + return meter->value_state() == desired_state; + return false; +} + static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, DOM::Element const& element, GC::Ptr shadow_host, MatchContext& context, GC::Ptr scope, SelectorKind selector_kind) { switch (pseudo_class.type) { @@ -502,6 +510,20 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla return element.matches_checked_pseudo_class(); case CSS::PseudoClass::Indeterminate: return matches_indeterminate_pseudo_class(element); + case CSS::PseudoClass::HighValue: + if (auto* meter = as_if(element)) + return meter->value() > meter->high(); + return false; + case CSS::PseudoClass::LowValue: + if (auto* meter = as_if(element)) + return meter->value() < meter->low(); + return false; + case CSS::PseudoClass::OptimalValue: + return matches_optimal_value_pseudo_class(element, HTML::HTMLMeterElement::ValueState::Optimal); + case CSS::PseudoClass::SuboptimalValue: + return matches_optimal_value_pseudo_class(element, HTML::HTMLMeterElement::ValueState::Suboptimal); + case CSS::PseudoClass::EvenLessGoodValue: + return matches_optimal_value_pseudo_class(element, HTML::HTMLMeterElement::ValueState::EvenLessGood); case CSS::PseudoClass::Defined: return element.is_defined(); case CSS::PseudoClass::Has: diff --git a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp index e901460d240..40edeafe877 100644 --- a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp @@ -201,6 +201,7 @@ void HTMLMeterElement::create_shadow_tree_if_needed() MUST(shadow_root->append_child(*meter_bar_element)); m_meter_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); + m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Fill); MUST(meter_bar_element->append_child(*m_meter_value_element)); update_meter_value_element(); } @@ -242,18 +243,6 @@ void HTMLMeterElement::update_meter_value_element() if (!m_meter_value_element) return; - switch (m_cached_value_state) { - case ValueState::Optimal: - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterOptimumValue); - break; - case ValueState::Suboptimal: - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue); - break; - case ValueState::EvenLessGood: - m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterEvenLessGoodValue); - break; - } - double position = (value - min) / (max - min) * 100; MUST(m_meter_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position)))); } From 3d3e77cd3ef320516d0aaf2a7e46756022baf4a2 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:32:14 +0100 Subject: [PATCH 057/141] Meta: Add explicit vcpkg dependency for zlib zlib is already included transitively for other dependencies, include it explicitly and link it with `LibCompress`. --- Libraries/LibCompress/CMakeLists.txt | 3 +++ vcpkg.json | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Libraries/LibCompress/CMakeLists.txt b/Libraries/LibCompress/CMakeLists.txt index 349581b71d0..17cce020c7c 100644 --- a/Libraries/LibCompress/CMakeLists.txt +++ b/Libraries/LibCompress/CMakeLists.txt @@ -7,3 +7,6 @@ set(SOURCES serenity_lib(LibCompress compress) target_link_libraries(LibCompress PRIVATE LibCore LibCrypto) + +find_package(ZLIB REQUIRED) +target_link_libraries(LibCompress PRIVATE ZLIB::ZLIB) diff --git a/vcpkg.json b/vcpkg.json index 2102a2c493c..5b5b93a3360 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -122,7 +122,8 @@ "name": "vulkan-headers", "platform": "!android" }, - "woff2" + "woff2", + "zlib" ], "overrides": [ { @@ -196,6 +197,10 @@ { "name": "woff2", "version": "1.0.2#4" + }, + { + "name": "zlib", + "version": "1.3.1" } ] } From 1612c1688e7cce839c6efc29a06590652d8f0022 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 23:23:09 +0100 Subject: [PATCH 058/141] LibWeb: Change hardcoded compression test to round trip As compression is not always deterministic, we cannot hardcode what it'll be like, do a round trip instead. --- .../Compression/CompressionStream.txt | 3 - .../Text/expected/Compression/round-trip.txt | 24 ++++++++ .../input/Compression/CompressionStream.html | 31 ---------- .../Text/input/Compression/round-trip.html | 56 +++++++++++++++++++ 4 files changed, 80 insertions(+), 34 deletions(-) delete mode 100644 Tests/LibWeb/Text/expected/Compression/CompressionStream.txt create mode 100644 Tests/LibWeb/Text/expected/Compression/round-trip.txt delete mode 100644 Tests/LibWeb/Text/input/Compression/CompressionStream.html create mode 100644 Tests/LibWeb/Text/input/Compression/round-trip.html diff --git a/Tests/LibWeb/Text/expected/Compression/CompressionStream.txt b/Tests/LibWeb/Text/expected/Compression/CompressionStream.txt deleted file mode 100644 index f9dc0b7f781..00000000000 --- a/Tests/LibWeb/Text/expected/Compression/CompressionStream.txt +++ /dev/null @@ -1,3 +0,0 @@ -format=deflate: eJwLT83JUchIzcnJV0grykzNSylWBABGEQb1 -format=deflate-raw: C0/NyVHISM3JyVdIK8pMzUspVgQA -format=gzip: H4sIAAAAAAADAwtPzclRyEjNyclXSCvKTM1LKVYEAHN0w4sTAAAA diff --git a/Tests/LibWeb/Text/expected/Compression/round-trip.txt b/Tests/LibWeb/Text/expected/Compression/round-trip.txt new file mode 100644 index 00000000000..889b6976811 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Compression/round-trip.txt @@ -0,0 +1,24 @@ +prefix=120,156 +equal=false +format=deflate: Well hello friends! +-------------- +prefix= +equal=false +format=deflate-raw: Well hello friends! +-------------- +prefix=31,139 +equal=false +format=gzip: Well hello friends! +-------------- +prefix=120,156 +equal=false +format=deflate: Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends! +-------------- +prefix= +equal=false +format=deflate-raw: Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends! +-------------- +prefix=31,139 +equal=false +format=gzip: Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends!Well hello friends! +-------------- diff --git a/Tests/LibWeb/Text/input/Compression/CompressionStream.html b/Tests/LibWeb/Text/input/Compression/CompressionStream.html deleted file mode 100644 index a0ffc4fa077..00000000000 --- a/Tests/LibWeb/Text/input/Compression/CompressionStream.html +++ /dev/null @@ -1,31 +0,0 @@ - - diff --git a/Tests/LibWeb/Text/input/Compression/round-trip.html b/Tests/LibWeb/Text/input/Compression/round-trip.html new file mode 100644 index 00000000000..2a34bd82ef8 --- /dev/null +++ b/Tests/LibWeb/Text/input/Compression/round-trip.html @@ -0,0 +1,56 @@ + + From 1c836588d91c1b8bdccf8aa52d27e3910ace2520 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 18:03:36 +0100 Subject: [PATCH 059/141] LibWeb: Add some compression WPT tests These are relevant for the next commits. --- .../compression-bad-chunks.tentative.any.txt | 27 +++++++ .../compression-large-flush-output.any.txt | 8 ++ ...pression-multiple-chunks.tentative.any.txt | 50 +++++++++++++ ...ecompression-split-chunk.tentative.any.txt | 50 +++++++++++++ .../compression-bad-chunks.tentative.any.html | 15 ++++ .../compression-bad-chunks.tentative.any.js | 74 +++++++++++++++++++ .../compression-large-flush-output.any.html | 16 ++++ .../compression-large-flush-output.any.js | 41 ++++++++++ ...ression-multiple-chunks.tentative.any.html | 15 ++++ ...mpression-multiple-chunks.tentative.any.js | 67 +++++++++++++++++ ...compression-split-chunk.tentative.any.html | 15 ++++ ...decompression-split-chunk.tentative.any.js | 53 +++++++++++++ .../resources/concatenate-stream.js | 25 +++++++ .../third_party/pako/pako_inflate.min.js | 1 + 14 files changed, 457 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/compression/compression-bad-chunks.tentative.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/compression/compression-multiple-chunks.tentative.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/resources/concatenate-stream.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/compression/third_party/pako/pako_inflate.min.js diff --git a/Tests/LibWeb/Text/expected/wpt-import/compression/compression-bad-chunks.tentative.any.txt b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-bad-chunks.tentative.any.txt new file mode 100644 index 00000000000..2c8da675484 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-bad-chunks.tentative.any.txt @@ -0,0 +1,27 @@ +Harness status: Error + +Found 21 tests + +15 Pass +6 Fail +Pass chunk of type undefined should error the stream for gzip +Pass chunk of type undefined should error the stream for deflate +Pass chunk of type undefined should error the stream for deflate-raw +Pass chunk of type null should error the stream for gzip +Pass chunk of type null should error the stream for deflate +Pass chunk of type null should error the stream for deflate-raw +Pass chunk of type numeric should error the stream for gzip +Pass chunk of type numeric should error the stream for deflate +Pass chunk of type numeric should error the stream for deflate-raw +Pass chunk of type object, not BufferSource should error the stream for gzip +Pass chunk of type object, not BufferSource should error the stream for deflate +Pass chunk of type object, not BufferSource should error the stream for deflate-raw +Pass chunk of type array should error the stream for gzip +Pass chunk of type array should error the stream for deflate +Pass chunk of type array should error the stream for deflate-raw +Fail chunk of type SharedArrayBuffer should error the stream for gzip +Fail chunk of type SharedArrayBuffer should error the stream for deflate +Fail chunk of type SharedArrayBuffer should error the stream for deflate-raw +Fail chunk of type shared Uint8Array should error the stream for gzip +Fail chunk of type shared Uint8Array should error the stream for deflate +Fail chunk of type shared Uint8Array should error the stream for deflate-raw \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt new file mode 100644 index 00000000000..a3ab97e1de4 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Fail +Fail deflate compression with large flush output +Fail gzip compression with large flush output +Fail deflate-raw compression with large flush output \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/compression/compression-multiple-chunks.tentative.any.txt b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-multiple-chunks.tentative.any.txt new file mode 100644 index 00000000000..b0453770210 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-multiple-chunks.tentative.any.txt @@ -0,0 +1,50 @@ +Harness status: OK + +Found 45 tests + +45 Pass +Pass compressing 2 chunks with deflate should work +Pass compressing 2 chunks with gzip should work +Pass compressing 2 chunks with deflate-raw should work +Pass compressing 3 chunks with deflate should work +Pass compressing 3 chunks with gzip should work +Pass compressing 3 chunks with deflate-raw should work +Pass compressing 4 chunks with deflate should work +Pass compressing 4 chunks with gzip should work +Pass compressing 4 chunks with deflate-raw should work +Pass compressing 5 chunks with deflate should work +Pass compressing 5 chunks with gzip should work +Pass compressing 5 chunks with deflate-raw should work +Pass compressing 6 chunks with deflate should work +Pass compressing 6 chunks with gzip should work +Pass compressing 6 chunks with deflate-raw should work +Pass compressing 7 chunks with deflate should work +Pass compressing 7 chunks with gzip should work +Pass compressing 7 chunks with deflate-raw should work +Pass compressing 8 chunks with deflate should work +Pass compressing 8 chunks with gzip should work +Pass compressing 8 chunks with deflate-raw should work +Pass compressing 9 chunks with deflate should work +Pass compressing 9 chunks with gzip should work +Pass compressing 9 chunks with deflate-raw should work +Pass compressing 10 chunks with deflate should work +Pass compressing 10 chunks with gzip should work +Pass compressing 10 chunks with deflate-raw should work +Pass compressing 11 chunks with deflate should work +Pass compressing 11 chunks with gzip should work +Pass compressing 11 chunks with deflate-raw should work +Pass compressing 12 chunks with deflate should work +Pass compressing 12 chunks with gzip should work +Pass compressing 12 chunks with deflate-raw should work +Pass compressing 13 chunks with deflate should work +Pass compressing 13 chunks with gzip should work +Pass compressing 13 chunks with deflate-raw should work +Pass compressing 14 chunks with deflate should work +Pass compressing 14 chunks with gzip should work +Pass compressing 14 chunks with deflate-raw should work +Pass compressing 15 chunks with deflate should work +Pass compressing 15 chunks with gzip should work +Pass compressing 15 chunks with deflate-raw should work +Pass compressing 16 chunks with deflate should work +Pass compressing 16 chunks with gzip should work +Pass compressing 16 chunks with deflate-raw should work \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt b/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt new file mode 100644 index 00000000000..eb7282ce1e1 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt @@ -0,0 +1,50 @@ +Harness status: Error + +Found 45 tests + +45 Fail +Fail decompressing splitted chunk into pieces of size 1 should work in deflate +Fail decompressing splitted chunk into pieces of size 1 should work in gzip +Fail decompressing splitted chunk into pieces of size 1 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 2 should work in deflate +Fail decompressing splitted chunk into pieces of size 2 should work in gzip +Fail decompressing splitted chunk into pieces of size 2 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 3 should work in deflate +Fail decompressing splitted chunk into pieces of size 3 should work in gzip +Fail decompressing splitted chunk into pieces of size 3 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 4 should work in deflate +Fail decompressing splitted chunk into pieces of size 4 should work in gzip +Fail decompressing splitted chunk into pieces of size 4 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 5 should work in deflate +Fail decompressing splitted chunk into pieces of size 5 should work in gzip +Fail decompressing splitted chunk into pieces of size 5 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 6 should work in deflate +Fail decompressing splitted chunk into pieces of size 6 should work in gzip +Fail decompressing splitted chunk into pieces of size 6 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 7 should work in deflate +Fail decompressing splitted chunk into pieces of size 7 should work in gzip +Fail decompressing splitted chunk into pieces of size 7 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 8 should work in deflate +Fail decompressing splitted chunk into pieces of size 8 should work in gzip +Fail decompressing splitted chunk into pieces of size 8 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 9 should work in deflate +Fail decompressing splitted chunk into pieces of size 9 should work in gzip +Fail decompressing splitted chunk into pieces of size 9 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 10 should work in deflate +Fail decompressing splitted chunk into pieces of size 10 should work in gzip +Fail decompressing splitted chunk into pieces of size 10 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 11 should work in deflate +Fail decompressing splitted chunk into pieces of size 11 should work in gzip +Fail decompressing splitted chunk into pieces of size 11 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 12 should work in deflate +Fail decompressing splitted chunk into pieces of size 12 should work in gzip +Fail decompressing splitted chunk into pieces of size 12 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 13 should work in deflate +Fail decompressing splitted chunk into pieces of size 13 should work in gzip +Fail decompressing splitted chunk into pieces of size 13 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 14 should work in deflate +Fail decompressing splitted chunk into pieces of size 14 should work in gzip +Fail decompressing splitted chunk into pieces of size 14 should work in deflate-raw +Fail decompressing splitted chunk into pieces of size 15 should work in deflate +Fail decompressing splitted chunk into pieces of size 15 should work in gzip +Fail decompressing splitted chunk into pieces of size 15 should work in deflate-raw \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.html b/Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.html new file mode 100644 index 00000000000..08632f26c4d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.js b/Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.js new file mode 100644 index 00000000000..2d0b5684733 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/compression-bad-chunks.tentative.any.js @@ -0,0 +1,74 @@ +// META: global=window,worker,shadowrealm + +'use strict'; + +const badChunks = [ + { + name: 'undefined', + value: undefined + }, + { + name: 'null', + value: null + }, + { + name: 'numeric', + value: 3.14 + }, + { + name: 'object, not BufferSource', + value: {} + }, + { + name: 'array', + value: [65] + }, + { + name: 'SharedArrayBuffer', + // Use a getter to postpone construction so that all tests don't fail where + // SharedArrayBuffer is not yet implemented. + get value() { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + return new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer; + } + }, + { + name: 'shared Uint8Array', + get value() { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + return new Uint8Array(new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer) + } + }, +]; + +for (const chunk of badChunks) { + promise_test(async t => { + const cs = new CompressionStream('gzip'); + const reader = cs.readable.getReader(); + const writer = cs.writable.getWriter(); + const writePromise = writer.write(chunk.value); + const readPromise = reader.read(); + await promise_rejects_js(t, TypeError, writePromise, 'write should reject'); + await promise_rejects_js(t, TypeError, readPromise, 'read should reject'); + }, `chunk of type ${chunk.name} should error the stream for gzip`); + + promise_test(async t => { + const cs = new CompressionStream('deflate'); + const reader = cs.readable.getReader(); + const writer = cs.writable.getWriter(); + const writePromise = writer.write(chunk.value); + const readPromise = reader.read(); + await promise_rejects_js(t, TypeError, writePromise, 'write should reject'); + await promise_rejects_js(t, TypeError, readPromise, 'read should reject'); + }, `chunk of type ${chunk.name} should error the stream for deflate`); + + promise_test(async t => { + const cs = new CompressionStream('deflate-raw'); + const reader = cs.readable.getReader(); + const writer = cs.writable.getWriter(); + const writePromise = writer.write(chunk.value); + const readPromise = reader.read(); + await promise_rejects_js(t, TypeError, writePromise, 'write should reject'); + await promise_rejects_js(t, TypeError, readPromise, 'read should reject'); + }, `chunk of type ${chunk.name} should error the stream for deflate-raw`); +} diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.html b/Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.html new file mode 100644 index 00000000000..ebc301ce7e5 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.html @@ -0,0 +1,16 @@ + + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.js b/Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.js new file mode 100644 index 00000000000..6afcb4d5287 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/compression-large-flush-output.any.js @@ -0,0 +1,41 @@ +// META: global=window,worker,shadowrealm +// META: script=third_party/pako/pako_inflate.min.js +// META: script=resources/concatenate-stream.js +// META: timeout=long + +'use strict'; + +// This test verifies that a large flush output will not truncate the +// final results. + +async function compressData(chunk, format) { + const cs = new CompressionStream(format); + const writer = cs.writable.getWriter(); + writer.write(chunk); + writer.close(); + return await concatenateStream(cs.readable); +} + +// JSON-encoded array of 10 thousands numbers ("[0,1,2,...]"). This produces 48_891 bytes of data. +const fullData = new TextEncoder().encode(JSON.stringify(Array.from({ length: 10_000 }, (_, i) => i))); +const data = fullData.subarray(0, 35_579); +const expectedValue = data; + +promise_test(async t => { + const compressedData = await compressData(data, 'deflate'); + // decompress with pako, and check that we got the same result as our original string + assert_array_equals(expectedValue, pako.inflate(compressedData), 'value should match'); +}, `deflate compression with large flush output`); + +promise_test(async t => { + const compressedData = await compressData(data, 'gzip'); + // decompress with pako, and check that we got the same result as our original string + assert_array_equals(expectedValue, pako.inflate(compressedData), 'value should match'); +}, `gzip compression with large flush output`); + +promise_test(async t => { + const compressedData = await compressData(data, 'deflate-raw'); + // decompress with pako, and check that we got the same result as our original string + assert_array_equals(expectedValue, pako.inflateRaw(compressedData), 'value should match'); +}, `deflate-raw compression with large flush output`); + diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.html b/Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.html new file mode 100644 index 00000000000..b6bbf8085fb --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.js b/Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.js new file mode 100644 index 00000000000..28a90e5ca53 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/compression-multiple-chunks.tentative.any.js @@ -0,0 +1,67 @@ +// META: global=window,worker,shadowrealm +// META: script=third_party/pako/pako_inflate.min.js +// META: timeout=long + +'use strict'; + +// This test asserts that compressing multiple chunks should work. + +// Example: ('Hello', 3) => TextEncoder().encode('HelloHelloHello') +function makeExpectedChunk(input, numberOfChunks) { + const expectedChunk = input.repeat(numberOfChunks); + return new TextEncoder().encode(expectedChunk); +} + +// Example: ('Hello', 3, 'deflate') => compress ['Hello', 'Hello', Hello'] +async function compressMultipleChunks(input, numberOfChunks, format) { + const cs = new CompressionStream(format); + const writer = cs.writable.getWriter(); + const chunk = new TextEncoder().encode(input); + for (let i = 0; i < numberOfChunks; ++i) { + writer.write(chunk); + } + const closePromise = writer.close(); + const out = []; + const reader = cs.readable.getReader(); + let totalSize = 0; + while (true) { + const { value, done } = await reader.read(); + if (done) + break; + out.push(value); + totalSize += value.byteLength; + } + await closePromise; + const concatenated = new Uint8Array(totalSize); + let offset = 0; + for (const array of out) { + concatenated.set(array, offset); + offset += array.byteLength; + } + return concatenated; +} + +const hello = 'Hello'; + +for (let numberOfChunks = 2; numberOfChunks <= 16; ++numberOfChunks) { + promise_test(async t => { + const compressedData = await compressMultipleChunks(hello, numberOfChunks, 'deflate'); + const expectedValue = makeExpectedChunk(hello, numberOfChunks); + // decompress with pako, and check that we got the same result as our original string + assert_array_equals(expectedValue, pako.inflate(compressedData), 'value should match'); + }, `compressing ${numberOfChunks} chunks with deflate should work`); + + promise_test(async t => { + const compressedData = await compressMultipleChunks(hello, numberOfChunks, 'gzip'); + const expectedValue = makeExpectedChunk(hello, numberOfChunks); + // decompress with pako, and check that we got the same result as our original string + assert_array_equals(expectedValue, pako.inflate(compressedData), 'value should match'); + }, `compressing ${numberOfChunks} chunks with gzip should work`); + + promise_test(async t => { + const compressedData = await compressMultipleChunks(hello, numberOfChunks, 'deflate-raw'); + const expectedValue = makeExpectedChunk(hello, numberOfChunks); + // decompress with pako, and check that we got the same result as our original string + assert_array_equals(expectedValue, pako.inflateRaw(compressedData), 'value should match'); + }, `compressing ${numberOfChunks} chunks with deflate-raw should work`); +} diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.html b/Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.html new file mode 100644 index 00000000000..727047f6b32 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.js b/Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.js new file mode 100644 index 00000000000..eb12c2a2360 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/decompression-split-chunk.tentative.any.js @@ -0,0 +1,53 @@ +// META: global=window,worker,shadowrealm + +'use strict'; + +const compressedBytesWithDeflate = new Uint8Array([120, 156, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 48, 173, 6, 36]); +const compressedBytesWithGzip = new Uint8Array([31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 176, 1, 57, 179, 15, 0, 0, 0]); +const compressedBytesWithDeflateRaw = new Uint8Array([ + 0x4b, 0xad, 0x28, 0x48, 0x4d, 0x2e, 0x49, 0x4d, 0x51, 0xc8, + 0x2f, 0x2d, 0x29, 0x28, 0x2d, 0x01, 0x00, +]); +const expectedChunkValue = new TextEncoder().encode('expected output'); + +async function decompressArrayBuffer(input, format, chunkSize) { + const ds = new DecompressionStream(format); + const reader = ds.readable.getReader(); + const writer = ds.writable.getWriter(); + for (let beginning = 0; beginning < input.length; beginning += chunkSize) { + writer.write(input.slice(beginning, beginning + chunkSize)); + } + writer.close(); + const out = []; + let totalSize = 0; + while (true) { + const { value, done } = await reader.read(); + if (done) break; + out.push(value); + totalSize += value.byteLength; + } + const concatenated = new Uint8Array(totalSize); + let offset = 0; + for (const array of out) { + concatenated.set(array, offset); + offset += array.byteLength; + } + return concatenated; +} + +for (let chunkSize = 1; chunkSize < 16; ++chunkSize) { + promise_test(async t => { + const decompressedData = await decompressArrayBuffer(compressedBytesWithDeflate, 'deflate', chunkSize); + assert_array_equals(decompressedData, expectedChunkValue, "value should match"); + }, `decompressing splitted chunk into pieces of size ${chunkSize} should work in deflate`); + + promise_test(async t => { + const decompressedData = await decompressArrayBuffer(compressedBytesWithGzip, 'gzip', chunkSize); + assert_array_equals(decompressedData, expectedChunkValue, "value should match"); + }, `decompressing splitted chunk into pieces of size ${chunkSize} should work in gzip`); + + promise_test(async t => { + const decompressedData = await decompressArrayBuffer(compressedBytesWithDeflateRaw, 'deflate-raw', chunkSize); + assert_array_equals(decompressedData, expectedChunkValue, "value should match"); + }, `decompressing splitted chunk into pieces of size ${chunkSize} should work in deflate-raw`); +} diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/resources/concatenate-stream.js b/Tests/LibWeb/Text/input/wpt-import/compression/resources/concatenate-stream.js new file mode 100644 index 00000000000..a35bb1416e7 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/resources/concatenate-stream.js @@ -0,0 +1,25 @@ +'use strict'; + +// Read all the chunks from a stream that returns BufferSource objects and +// concatenate them into a single Uint8Array. +async function concatenateStream(readableStream) { + const reader = readableStream.getReader(); + let totalSize = 0; + const buffers = []; + while (true) { + const { value, done } = await reader.read(); + if (done) { + break; + } + buffers.push(value); + totalSize += value.byteLength; + } + reader.releaseLock(); + const concatenated = new Uint8Array(totalSize); + let offset = 0; + for (const buffer of buffers) { + concatenated.set(buffer, offset); + offset += buffer.byteLength; + } + return concatenated; +} diff --git a/Tests/LibWeb/Text/input/wpt-import/compression/third_party/pako/pako_inflate.min.js b/Tests/LibWeb/Text/input/wpt-import/compression/third_party/pako/pako_inflate.min.js new file mode 100644 index 00000000000..a191a78a895 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/compression/third_party/pako/pako_inflate.min.js @@ -0,0 +1 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).pako=e()}}(function(){return function r(o,s,f){function l(t,e){if(!s[t]){if(!o[t]){var i="function"==typeof require&&require;if(!e&&i)return i(t,!0);if(d)return d(t,!0);var n=new Error("Cannot find module '"+t+"'");throw n.code="MODULE_NOT_FOUND",n}var a=s[t]={exports:{}};o[t][0].call(a.exports,function(e){return l(o[t][1][e]||e)},a,a.exports,r,o,s,f)}return s[t].exports}for(var d="function"==typeof require&&require,e=0;e>>6:(i<65536?t[r++]=224|i>>>12:(t[r++]=240|i>>>18,t[r++]=128|i>>>12&63),t[r++]=128|i>>>6&63),t[r++]=128|63&i);return t},i.buf2binstring=function(e){return d(e,e.length)},i.binstring2buf=function(e){for(var t=new f.Buf8(e.length),i=0,n=t.length;i>10&1023,s[n++]=56320|1023&a)}return d(s,n)},i.utf8border=function(e,t){var i;for((t=t||e.length)>e.length&&(t=e.length),i=t-1;0<=i&&128==(192&e[i]);)i--;return i<0?t:0===i?t:i+l[e[i]]>t?i:t}},{"./common":1}],3:[function(e,t,i){"use strict";t.exports=function(e,t,i,n){for(var a=65535&e|0,r=e>>>16&65535|0,o=0;0!==i;){for(i-=o=2e3>>1:e>>>1;t[i]=e}return t}();t.exports=function(e,t,i,n){var a=s,r=n+i;e^=-1;for(var o=n;o>>8^a[255&(e^t[o])];return-1^e}},{}],6:[function(e,t,i){"use strict";t.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},{}],7:[function(e,t,i){"use strict";t.exports=function(e,t){var i,n,a,r,o,s,f,l,d,c,u,h,b,m,w,k,_,g,v,p,x,y,S,E,Z;i=e.state,n=e.next_in,E=e.input,a=n+(e.avail_in-5),r=e.next_out,Z=e.output,o=r-(t-e.avail_out),s=r+(e.avail_out-257),f=i.dmax,l=i.wsize,d=i.whave,c=i.wnext,u=i.window,h=i.hold,b=i.bits,m=i.lencode,w=i.distcode,k=(1<>>=v=g>>>24,b-=v,0===(v=g>>>16&255))Z[r++]=65535&g;else{if(!(16&v)){if(0==(64&v)){g=m[(65535&g)+(h&(1<>>=v,b-=v),b<15&&(h+=E[n++]<>>=v=g>>>24,b-=v,!(16&(v=g>>>16&255))){if(0==(64&v)){g=w[(65535&g)+(h&(1<>>=v,b-=v,(v=r-o)>3,h&=(1<<(b-=p<<3))-1,e.next_in=n,e.next_out=r,e.avail_in=n>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function r(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new z.Buf16(320),this.work=new z.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function o(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=F,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new z.Buf32(n),t.distcode=t.distdyn=new z.Buf32(a),t.sane=1,t.back=-1,T):U}function s(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,o(e)):U}function f(e,t){var i,n;return e&&e.state?(n=e.state,t<0?(i=0,t=-t):(i=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15=r.wsize?(z.arraySet(r.window,t,i-r.wsize,r.wsize,0),r.wnext=0,r.whave=r.wsize):(n<(a=r.wsize-r.wnext)&&(a=n),z.arraySet(r.window,t,i-n,a,r.wnext),(n-=a)?(z.arraySet(r.window,t,i-n,n,0),r.wnext=n,r.whave=r.wsize):(r.wnext+=a,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,i.check=N(i.check,B,2,0),d=l=0,i.mode=2;break}if(i.flags=0,i.head&&(i.head.done=!1),!(1&i.wrap)||(((255&l)<<8)+(l>>8))%31){e.msg="incorrect header check",i.mode=30;break}if(8!=(15&l)){e.msg="unknown compression method",i.mode=30;break}if(d-=4,x=8+(15&(l>>>=4)),0===i.wbits)i.wbits=x;else if(x>i.wbits){e.msg="invalid window size",i.mode=30;break}i.dmax=1<>8&1),512&i.flags&&(B[0]=255&l,B[1]=l>>>8&255,i.check=N(i.check,B,2,0)),d=l=0,i.mode=3;case 3:for(;d<32;){if(0===s)break e;s--,l+=n[r++]<>>8&255,B[2]=l>>>16&255,B[3]=l>>>24&255,i.check=N(i.check,B,4,0)),d=l=0,i.mode=4;case 4:for(;d<16;){if(0===s)break e;s--,l+=n[r++]<>8),512&i.flags&&(B[0]=255&l,B[1]=l>>>8&255,i.check=N(i.check,B,2,0)),d=l=0,i.mode=5;case 5:if(1024&i.flags){for(;d<16;){if(0===s)break e;s--,l+=n[r++]<>>8&255,i.check=N(i.check,B,2,0)),d=l=0}else i.head&&(i.head.extra=null);i.mode=6;case 6:if(1024&i.flags&&(s<(h=i.length)&&(h=s),h&&(i.head&&(x=i.head.extra_len-i.length,i.head.extra||(i.head.extra=new Array(i.head.extra_len)),z.arraySet(i.head.extra,n,r,h,x)),512&i.flags&&(i.check=N(i.check,n,h,r)),s-=h,r+=h,i.length-=h),i.length))break e;i.length=0,i.mode=7;case 7:if(2048&i.flags){if(0===s)break e;for(h=0;x=n[r+h++],i.head&&x&&i.length<65536&&(i.head.name+=String.fromCharCode(x)),x&&h>9&1,i.head.done=!0),e.adler=i.check=0,i.mode=12;break;case 10:for(;d<32;){if(0===s)break e;s--,l+=n[r++]<>>=7&d,d-=7&d,i.mode=27;break}for(;d<3;){if(0===s)break e;s--,l+=n[r++]<>>=1)){case 0:i.mode=14;break;case 1:if(H(i),i.mode=20,6!==t)break;l>>>=2,d-=2;break e;case 2:i.mode=17;break;case 3:e.msg="invalid block type",i.mode=30}l>>>=2,d-=2;break;case 14:for(l>>>=7&d,d-=7&d;d<32;){if(0===s)break e;s--,l+=n[r++]<>>16^65535)){e.msg="invalid stored block lengths",i.mode=30;break}if(i.length=65535&l,d=l=0,i.mode=15,6===t)break e;case 15:i.mode=16;case 16:if(h=i.length){if(s>>=5,d-=5,i.ndist=1+(31&l),l>>>=5,d-=5,i.ncode=4+(15&l),l>>>=4,d-=4,286>>=3,d-=3}for(;i.have<19;)i.lens[A[i.have++]]=0;if(i.lencode=i.lendyn,i.lenbits=7,S={bits:i.lenbits},y=C(0,i.lens,0,19,i.lencode,0,i.work,S),i.lenbits=S.bits,y){e.msg="invalid code lengths set",i.mode=30;break}i.have=0,i.mode=19;case 19:for(;i.have>>16&255,_=65535&Z,!((w=Z>>>24)<=d);){if(0===s)break e;s--,l+=n[r++]<>>=w,d-=w,i.lens[i.have++]=_;else{if(16===_){for(E=w+2;d>>=w,d-=w,0===i.have){e.msg="invalid bit length repeat",i.mode=30;break}x=i.lens[i.have-1],h=3+(3&l),l>>>=2,d-=2}else if(17===_){for(E=w+3;d>>=w)),l>>>=3,d-=3}else{for(E=w+7;d>>=w)),l>>>=7,d-=7}if(i.have+h>i.nlen+i.ndist){e.msg="invalid bit length repeat",i.mode=30;break}for(;h--;)i.lens[i.have++]=x}}if(30===i.mode)break;if(0===i.lens[256]){e.msg="invalid code -- missing end-of-block",i.mode=30;break}if(i.lenbits=9,S={bits:i.lenbits},y=C(I,i.lens,0,i.nlen,i.lencode,0,i.work,S),i.lenbits=S.bits,y){e.msg="invalid literal/lengths set",i.mode=30;break}if(i.distbits=6,i.distcode=i.distdyn,S={bits:i.distbits},y=C(D,i.lens,i.nlen,i.ndist,i.distcode,0,i.work,S),i.distbits=S.bits,y){e.msg="invalid distances set",i.mode=30;break}if(i.mode=20,6===t)break e;case 20:i.mode=21;case 21:if(6<=s&&258<=f){e.next_out=o,e.avail_out=f,e.next_in=r,e.avail_in=s,i.hold=l,i.bits=d,O(e,u),o=e.next_out,a=e.output,f=e.avail_out,r=e.next_in,n=e.input,s=e.avail_in,l=i.hold,d=i.bits,12===i.mode&&(i.back=-1);break}for(i.back=0;k=(Z=i.lencode[l&(1<>>16&255,_=65535&Z,!((w=Z>>>24)<=d);){if(0===s)break e;s--,l+=n[r++]<>g)])>>>16&255,_=65535&Z,!(g+(w=Z>>>24)<=d);){if(0===s)break e;s--,l+=n[r++]<>>=g,d-=g,i.back+=g}if(l>>>=w,d-=w,i.back+=w,i.length=_,0===k){i.mode=26;break}if(32&k){i.back=-1,i.mode=12;break}if(64&k){e.msg="invalid literal/length code",i.mode=30;break}i.extra=15&k,i.mode=22;case 22:if(i.extra){for(E=i.extra;d>>=i.extra,d-=i.extra,i.back+=i.extra}i.was=i.length,i.mode=23;case 23:for(;k=(Z=i.distcode[l&(1<>>16&255,_=65535&Z,!((w=Z>>>24)<=d);){if(0===s)break e;s--,l+=n[r++]<>g)])>>>16&255,_=65535&Z,!(g+(w=Z>>>24)<=d);){if(0===s)break e;s--,l+=n[r++]<>>=g,d-=g,i.back+=g}if(l>>>=w,d-=w,i.back+=w,64&k){e.msg="invalid distance code",i.mode=30;break}i.offset=_,i.extra=15&k,i.mode=24;case 24:if(i.extra){for(E=i.extra;d>>=i.extra,d-=i.extra,i.back+=i.extra}if(i.offset>i.dmax){e.msg="invalid distance too far back",i.mode=30;break}i.mode=25;case 25:if(0===f)break e;if(h=u-f,i.offset>h){if((h=i.offset-h)>i.whave&&i.sane){e.msg="invalid distance too far back",i.mode=30;break}h>i.wnext?(h-=i.wnext,b=i.wsize-h):b=i.wnext-h,h>i.length&&(h=i.length),m=i.window}else m=a,b=o-i.offset,h=i.length;for(fh?(m=O[C+o[g]],w=A[z+o[g]]):(m=96,w=0),f=1<<_-S,v=l=1<>S)+(l-=f)]=b<<24|m<<16|w|0,0!==l;);for(f=1<<_-1;B&f;)f>>=1;if(0!==f?(B&=f-1,B+=f):B=0,g++,0==--R[_]){if(_===p)break;_=t[i+o[g]]}if(x<_&&(B&c)!==d){for(0===S&&(S=x),u+=v,E=1<<(y=_-S);y+S Date: Sat, 1 Mar 2025 17:33:52 +0100 Subject: [PATCH 060/141] LibCompress: Introduce generic de/compressor using zlib This compressor decompressor is capable of handling zlib, gzip and deflate all in one. --- Libraries/LibCompress/CMakeLists.txt | 3 +- Libraries/LibCompress/GenericZlib.cpp | 219 ++++++++++++++++++++++++++ Libraries/LibCompress/GenericZlib.h | 101 ++++++++++++ 3 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 Libraries/LibCompress/GenericZlib.cpp create mode 100644 Libraries/LibCompress/GenericZlib.h diff --git a/Libraries/LibCompress/CMakeLists.txt b/Libraries/LibCompress/CMakeLists.txt index 17cce020c7c..d5fb08674b4 100644 --- a/Libraries/LibCompress/CMakeLists.txt +++ b/Libraries/LibCompress/CMakeLists.txt @@ -1,8 +1,9 @@ set(SOURCES Deflate.cpp + GenericZlib.cpp + Gzip.cpp PackBitsDecoder.cpp Zlib.cpp - Gzip.cpp ) serenity_lib(LibCompress compress) diff --git a/Libraries/LibCompress/GenericZlib.cpp b/Libraries/LibCompress/GenericZlib.cpp new file mode 100644 index 00000000000..b8ff099744d --- /dev/null +++ b/Libraries/LibCompress/GenericZlib.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2025, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include + +namespace Compress { + +static Error handle_zlib_error(int ret) +{ + switch (ret) { + case Z_ERRNO: + return Error::from_errno(errno); + case Z_DATA_ERROR: + // Z_DATA_ERROR if the input data was corrupted + return Error::from_string_literal("zlib data error"); + case Z_STREAM_ERROR: + // Z_STREAM_ERROR if the parameters are invalid, such as a null pointer to the structure + return Error::from_string_literal("zlib stream error"); + case Z_VERSION_ERROR: + // Z_VERSION_ERROR if the zlib library version is incompatible with the version assumed by the caller + return Error::from_string_literal("zlib version mismatch"); + case Z_MEM_ERROR: + // Z_MEM_ERROR if there was not enough memory + return Error::from_errno(ENOMEM); + default: + VERIFY_NOT_REACHED(); + } +} + +GenericZlibDecompressor::GenericZlibDecompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : m_stream(move(stream)) + , m_zstream(zstream) + , m_buffer(move(buffer)) +{ +} + +ErrorOr GenericZlibDecompressor::new_z_stream(int window_bits) +{ + auto zstream = new (nothrow) z_stream {}; + if (!zstream) + return Error::from_errno(ENOMEM); + + // The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller. + zstream->next_in = nullptr; + zstream->avail_in = 0; + zstream->zalloc = nullptr; + zstream->zfree = nullptr; + zstream->opaque = nullptr; + + if (auto ret = inflateInit2(zstream, window_bits); ret != Z_OK) + return handle_zlib_error(ret); + + return zstream; +} + +GenericZlibDecompressor::~GenericZlibDecompressor() +{ + inflateEnd(m_zstream); + delete m_zstream; +} + +ErrorOr GenericZlibDecompressor::read_some(Bytes bytes) +{ + m_zstream->avail_out = bytes.size(); + m_zstream->next_out = bytes.data(); + + if (m_zstream->avail_in == 0) { + auto in = TRY(m_stream->read_some(m_buffer.span())); + m_zstream->avail_in = in.size(); + m_zstream->next_in = m_buffer.data(); + } + + auto ret = inflate(m_zstream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) + return handle_zlib_error(ret); + + if (ret == Z_STREAM_END) { + inflateReset(m_zstream); + if (m_zstream->avail_in == 0) + m_eof = true; + } + + return bytes.slice(0, bytes.size() - m_zstream->avail_out); +} + +ErrorOr GenericZlibDecompressor::write_some(ReadonlyBytes) +{ + return Error::from_errno(EBADF); +} + +bool GenericZlibDecompressor::is_eof() const +{ + return m_eof; +} + +bool GenericZlibDecompressor::is_open() const +{ + return m_stream->is_open(); +} + +void GenericZlibDecompressor::close() +{ +} + +GenericZlibCompressor::GenericZlibCompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : m_stream(move(stream)) + , m_zstream(zstream) + , m_buffer(move(buffer)) +{ +} + +ErrorOr GenericZlibCompressor::new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level) +{ + auto zstream = new (nothrow) z_stream {}; + if (!zstream) + return Error::from_errno(ENOMEM); + + // The fields zalloc, zfree and opaque must be initialized before by the caller. + zstream->zalloc = nullptr; + zstream->zfree = nullptr; + zstream->opaque = nullptr; + + int level = [&] { + switch (compression_level) { + case GenericZlibCompressionLevel::Fastest: + return Z_BEST_SPEED; + case GenericZlibCompressionLevel::Default: + return Z_DEFAULT_COMPRESSION; + case GenericZlibCompressionLevel::Best: + return Z_BEST_COMPRESSION; + default: + VERIFY_NOT_REACHED(); + } + }(); + + if (auto ret = deflateInit2(zstream, level, Z_DEFLATED, window_bits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); ret != Z_OK) + return handle_zlib_error(ret); + + return zstream; +} + +GenericZlibCompressor::~GenericZlibCompressor() +{ + deflateEnd(m_zstream); + delete m_zstream; +} + +ErrorOr GenericZlibCompressor::read_some(Bytes) +{ + return Error::from_errno(EBADF); +} + +ErrorOr GenericZlibCompressor::write_some(ReadonlyBytes bytes) +{ + m_zstream->avail_in = bytes.size(); + m_zstream->next_in = const_cast(bytes.data()); + + // If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter + // and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out). + do { + m_zstream->avail_out = m_buffer.size(); + m_zstream->next_out = m_buffer.data(); + + auto ret = deflate(m_zstream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_BUF_ERROR) + return handle_zlib_error(ret); + + auto have = m_buffer.size() - m_zstream->avail_out; + TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have))); + } while (m_zstream->avail_out == 0); + + VERIFY(m_zstream->avail_in == 0); + return bytes.size(); +} + +bool GenericZlibCompressor::is_eof() const +{ + return false; +} + +bool GenericZlibCompressor::is_open() const +{ + return m_stream->is_open(); +} + +void GenericZlibCompressor::close() +{ +} + +ErrorOr GenericZlibCompressor::finish() +{ + VERIFY(m_zstream->avail_in == 0); + + // If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END + // if there was enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this function must be called again with Z_FINISH + // and more output space (updated avail_out) but no more input data, until it returns with Z_STREAM_END or an error. + while (true) { + m_zstream->avail_out = m_buffer.size(); + m_zstream->next_out = m_buffer.data(); + + auto ret = deflate(m_zstream, Z_FINISH); + if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || ret == Z_OK) { + auto have = m_buffer.size() - m_zstream->avail_out; + TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have))); + + if (ret == Z_STREAM_END) + return {}; + } else { + return handle_zlib_error(ret); + } + } +} + +} diff --git a/Libraries/LibCompress/GenericZlib.h b/Libraries/LibCompress/GenericZlib.h new file mode 100644 index 00000000000..a2db6fefc80 --- /dev/null +++ b/Libraries/LibCompress/GenericZlib.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +extern "C" { +typedef struct z_stream_s z_stream; +} + +namespace Compress { + +enum class GenericZlibCompressionLevel : u8 { + Fastest, + Default, + Best, +}; + +class GenericZlibDecompressor : public Stream { + AK_MAKE_NONCOPYABLE(GenericZlibDecompressor); + +public: + ~GenericZlibDecompressor() override; + + virtual ErrorOr read_some(Bytes) override; + virtual ErrorOr write_some(ReadonlyBytes) override; + virtual bool is_eof() const override; + virtual bool is_open() const override; + virtual void close() override; + +protected: + GenericZlibDecompressor(AK::FixedArray, MaybeOwned, z_stream*); + + static ErrorOr new_z_stream(int window_bits); + +private: + MaybeOwned m_stream; + z_stream* m_zstream; + + bool m_eof { false }; + + AK::FixedArray m_buffer; +}; + +class GenericZlibCompressor : public Stream { + AK_MAKE_NONCOPYABLE(GenericZlibCompressor); + +public: + ~GenericZlibCompressor() override; + + virtual ErrorOr read_some(Bytes) override; + virtual ErrorOr write_some(ReadonlyBytes) override; + virtual bool is_eof() const override; + virtual bool is_open() const override; + virtual void close() override; + ErrorOr finish(); + +protected: + GenericZlibCompressor(AK::FixedArray, MaybeOwned, z_stream*); + + static ErrorOr new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level); + +private: + MaybeOwned m_stream; + z_stream* m_zstream; + + AK::FixedArray m_buffer; +}; + +template +ErrorOr decompress_all(ReadonlyBytes bytes) +{ + auto input_stream = make(bytes); + auto deflate_stream = TRY(T::create(MaybeOwned(move(input_stream)))); + return TRY(deflate_stream->read_until_eof(4096)); +} + +template +ErrorOr compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level) +{ + auto output_stream = TRY(try_make()); + auto gzip_stream = TRY(T::create(MaybeOwned { *output_stream }, compression_level)); + + TRY(gzip_stream->write_until_depleted(bytes)); + TRY(gzip_stream->finish()); + + auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size())); + TRY(output_stream->read_until_filled(buffer.bytes())); + + return buffer; +} + +} From dafbe3262604cec8cadc95f1efca1277115731e7 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:37:59 +0100 Subject: [PATCH 061/141] LibCompress: Refactor zlib de/compressor using zlib --- Libraries/LibCompress/Zlib.cpp | 165 ++---------------- Libraries/LibCompress/Zlib.h | 82 ++------- .../LibWeb/Compression/CompressionStream.cpp | 2 +- Tests/LibCompress/TestZlib.cpp | 58 +++--- 4 files changed, 63 insertions(+), 244 deletions(-) diff --git a/Libraries/LibCompress/Zlib.cpp b/Libraries/LibCompress/Zlib.cpp index 5edc542b1cb..2e88eb3ad48 100644 --- a/Libraries/LibCompress/Zlib.cpp +++ b/Libraries/LibCompress/Zlib.cpp @@ -1,175 +1,38 @@ /* * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include -#include -#include -#include -#include #include +#include + namespace Compress { ErrorOr> ZlibDecompressor::create(MaybeOwned stream) { - return adopt_nonnull_own_or_enomem(new (nothrow) ZlibDecompressor(move(stream))); + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibDecompressor::new_z_stream(MAX_WBITS)); + return adopt_nonnull_own_or_enomem(new (nothrow) ZlibDecompressor(move(buffer), move(stream), zstream)); } -ZlibDecompressor::ZlibDecompressor(MaybeOwned stream) - : m_has_seen_header(false) - , m_stream(move(stream)) +ErrorOr ZlibDecompressor::decompress_all(ReadonlyBytes bytes) { + return ::Compress::decompress_all(bytes); } -ErrorOr ZlibDecompressor::read_some(Bytes bytes) +ErrorOr> ZlibCompressor::create(MaybeOwned stream, GenericZlibCompressionLevel compression_level) { - if (!m_has_seen_header) { - auto header = TRY(m_stream->read_value()); - - if (header.compression_method != ZlibCompressionMethod::Deflate || header.compression_info > 7) - return Error::from_string_literal("Non-DEFLATE compression inside Zlib is not supported"); - - if (header.present_dictionary) - return Error::from_string_literal("Zlib compression with a pre-defined dictionary is currently not supported"); - - if (header.as_u16 % 31 != 0) - return Error::from_string_literal("Zlib error correction code does not match"); - - auto bit_stream = make(move(m_stream)); - auto deflate_stream = TRY(Compress::DeflateDecompressor::construct(move(bit_stream))); - - m_stream = move(deflate_stream); - m_has_seen_header = true; - } - return m_stream->read_some(bytes); + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibCompressor::new_z_stream(MAX_WBITS, compression_level)); + return adopt_nonnull_own_or_enomem(new (nothrow) ZlibCompressor(move(buffer), move(stream), zstream)); } -ErrorOr ZlibDecompressor::write_some(ReadonlyBytes) +ErrorOr ZlibCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level) { - return Error::from_errno(EBADF); -} - -bool ZlibDecompressor::is_eof() const -{ - return m_stream->is_eof(); -} - -bool ZlibDecompressor::is_open() const -{ - return m_stream->is_open(); -} - -void ZlibDecompressor::close() -{ -} - -ErrorOr> ZlibCompressor::construct(MaybeOwned stream, ZlibCompressionLevel compression_level) -{ - // Zlib only defines Deflate as a compression method. - auto compression_method = ZlibCompressionMethod::Deflate; - - // FIXME: Find a way to compress with Deflate's "Best" compression level. - auto compressor_stream = TRY(DeflateCompressor::construct(MaybeOwned(*stream), static_cast(compression_level))); - - auto zlib_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) ZlibCompressor(move(stream), move(compressor_stream)))); - TRY(zlib_compressor->write_header(compression_method, compression_level)); - - return zlib_compressor; -} - -ZlibCompressor::ZlibCompressor(MaybeOwned stream, NonnullOwnPtr compressor_stream) - : m_output_stream(move(stream)) - , m_compressor(move(compressor_stream)) -{ -} - -ZlibCompressor::~ZlibCompressor() = default; - -ErrorOr ZlibCompressor::write_header(ZlibCompressionMethod compression_method, ZlibCompressionLevel compression_level) -{ - u8 compression_info = 0; - if (compression_method == ZlibCompressionMethod::Deflate) { - compression_info = AK::log2(DeflateCompressor::window_size) - 8; - VERIFY(compression_info <= 7); - } - - ZlibHeader header { - .compression_method = compression_method, - .compression_info = compression_info, - .check_bits = 0, - .present_dictionary = false, - .compression_level = compression_level, - }; - header.check_bits = 0b11111 - header.as_u16 % 31; - - // FIXME: Support pre-defined dictionaries. - - TRY(m_output_stream->write_value(header.as_u16)); - - return {}; -} - -ErrorOr ZlibCompressor::read_some(Bytes) -{ - return Error::from_errno(EBADF); -} - -ErrorOr ZlibCompressor::write_some(ReadonlyBytes bytes) -{ - VERIFY(!m_finished); - - size_t n_written = TRY(m_compressor->write_some(bytes)); - m_adler32_checksum.update(bytes.trim(n_written)); - return n_written; -} - -bool ZlibCompressor::is_eof() const -{ - return false; -} - -bool ZlibCompressor::is_open() const -{ - return m_output_stream->is_open(); -} - -void ZlibCompressor::close() -{ -} - -ErrorOr ZlibCompressor::finish() -{ - VERIFY(!m_finished); - - if (is(m_compressor.ptr())) - TRY(static_cast(m_compressor.ptr())->final_flush()); - - NetworkOrdered adler_sum = m_adler32_checksum.digest(); - TRY(m_output_stream->write_value(adler_sum)); - - m_finished = true; - - return {}; -} - -ErrorOr ZlibCompressor::compress_all(ReadonlyBytes bytes, ZlibCompressionLevel compression_level) -{ - auto output_stream = TRY(try_make()); - auto zlib_stream = TRY(ZlibCompressor::construct(MaybeOwned(*output_stream), compression_level)); - - TRY(zlib_stream->write_until_depleted(bytes)); - - TRY(zlib_stream->finish()); - - auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size())); - TRY(output_stream->read_until_filled(buffer.bytes())); - - return buffer; + return ::Compress::compress_all(bytes, compression_level); } } diff --git a/Libraries/LibCompress/Zlib.h b/Libraries/LibCompress/Zlib.h index 13d2e24be9c..0c2a3224b3f 100644 --- a/Libraries/LibCompress/Zlib.h +++ b/Libraries/LibCompress/Zlib.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,87 +8,34 @@ #pragma once #include -#include #include -#include -#include -#include #include -#include -#include +#include namespace Compress { -enum class ZlibCompressionMethod : u8 { - Deflate = 8, -}; - -enum class ZlibCompressionLevel : u8 { - Fastest, - Fast, - Default, - Best, -}; - -struct ZlibHeader { - union { - struct { - ZlibCompressionMethod compression_method : 4; - u8 compression_info : 4; - - u8 check_bits : 5; - bool present_dictionary : 1; - ZlibCompressionLevel compression_level : 2; - }; - NetworkOrdered as_u16; - }; -}; -static_assert(sizeof(ZlibHeader) == sizeof(u16)); - -class ZlibDecompressor : public Stream { +class ZlibDecompressor final : public GenericZlibDecompressor { public: static ErrorOr> create(MaybeOwned); - - virtual ErrorOr read_some(Bytes) override; - virtual ErrorOr write_some(ReadonlyBytes) override; - virtual bool is_eof() const override; - virtual bool is_open() const override; - virtual void close() override; + static ErrorOr decompress_all(ReadonlyBytes); private: - ZlibDecompressor(MaybeOwned); - - bool m_has_seen_header { false }; - MaybeOwned m_stream; + ZlibDecompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibDecompressor(move(buffer), move(stream), zstream) + { + } }; -class ZlibCompressor : public Stream { +class ZlibCompressor final : public GenericZlibCompressor { public: - static ErrorOr> construct(MaybeOwned, ZlibCompressionLevel = ZlibCompressionLevel::Default); - ~ZlibCompressor(); - - virtual ErrorOr read_some(Bytes) override; - virtual ErrorOr write_some(ReadonlyBytes) override; - virtual bool is_eof() const override; - virtual bool is_open() const override; - virtual void close() override; - ErrorOr finish(); - - static ErrorOr compress_all(ReadonlyBytes bytes, ZlibCompressionLevel = ZlibCompressionLevel::Default); + static ErrorOr> create(MaybeOwned, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); + static ErrorOr compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); private: - ZlibCompressor(MaybeOwned stream, NonnullOwnPtr compressor_stream); - ErrorOr write_header(ZlibCompressionMethod, ZlibCompressionLevel); - - bool m_finished { false }; - MaybeOwned m_output_stream; - NonnullOwnPtr m_compressor; - Crypto::Checksum::Adler32 m_adler32_checksum; + ZlibCompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibCompressor(move(buffer), move(stream), zstream) + { + } }; } - -template<> -struct AK::Traits : public AK::DefaultTraits { - static constexpr bool is_trivially_serializable() { return true; } -}; diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index 831bc9d6704..01304a49e02 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -31,7 +31,7 @@ WebIDL::ExceptionOr> CompressionStream::construct_imp auto compressor = [&, input_stream = MaybeOwned { *input_stream }]() mutable -> ErrorOr { switch (format) { case Bindings::CompressionFormat::Deflate: - return TRY(Compress::ZlibCompressor::construct(move(input_stream))); + return TRY(Compress::ZlibCompressor::create(move(input_stream))); case Bindings::CompressionFormat::DeflateRaw: return TRY(Compress::DeflateCompressor::construct(make(move(input_stream)))); case Bindings::CompressionFormat::Gzip: diff --git a/Tests/LibCompress/TestZlib.cpp b/Tests/LibCompress/TestZlib.cpp index 5d58ae0c90b..09e1eb02cb8 100644 --- a/Tests/LibCompress/TestZlib.cpp +++ b/Tests/LibCompress/TestZlib.cpp @@ -4,11 +4,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include - -#include +#include +#include #include #include +#include TEST_CASE(zlib_decompress_simple) { @@ -21,9 +21,7 @@ TEST_CASE(zlib_decompress_simple) u8 const uncompressed[] = "This is a simple text file :)"; - auto stream = make(compressed); - auto decompressor = TRY_OR_FAIL(Compress::ZlibDecompressor::create(move(stream))); - auto decompressed = TRY_OR_FAIL(decompressor->read_until_eof()); + auto decompressed = TRY_OR_FAIL(Compress::ZlibDecompressor::decompress_all(compressed)); EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } @@ -46,31 +44,43 @@ TEST_CASE(zlib_decompress_stream) EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } -TEST_CASE(zlib_compress_simple) +TEST_CASE(zlib_round_trip_simple_default) { - // Note: This is just the output of our compression function from an arbitrary point in time. - // This test is intended to ensure that the decompression doesn't change unintentionally, - // it does not make any guarantees for correctness. - - Array const compressed { - 0x78, 0x9C, 0x0B, 0xC9, 0xC8, 0x2C, 0x56, 0xC8, 0x2C, 0x56, 0x48, 0x54, - 0x28, 0xCE, 0xCC, 0x2D, 0xC8, 0x49, 0x55, 0x28, 0x49, 0xAD, 0x28, 0x51, - 0x48, 0xCB, 0xCC, 0x49, 0x55, 0xB0, 0xD2, 0x04, 0x00, 0x99, 0x5E, 0x09, - 0xE8 - }; - u8 const uncompressed[] = "This is a simple text file :)"; - auto const freshly_pressed = Compress::ZlibCompressor::compress_all({ uncompressed, sizeof(uncompressed) - 1 }); - EXPECT(freshly_pressed.value().bytes() == compressed.span()); + auto const freshly_pressed = TRY_OR_FAIL(Compress::ZlibCompressor::compress_all({ uncompressed, sizeof(uncompressed) - 1 }, Compress::GenericZlibCompressionLevel::Default)); + EXPECT(freshly_pressed.span().slice(0, 2) == ReadonlyBytes { { 0x78, 0x9C } }); + + auto const decompressed = TRY_OR_FAIL(Compress::ZlibDecompressor::decompress_all(freshly_pressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(zlib_round_trip_simple_best) +{ + u8 const uncompressed[] = "This is a simple text file :)"; + + auto const freshly_pressed = TRY_OR_FAIL(Compress::ZlibCompressor::compress_all({ uncompressed, sizeof(uncompressed) - 1 }, Compress::GenericZlibCompressionLevel::Best)); + EXPECT(freshly_pressed.span().slice(0, 2) == ReadonlyBytes { { 0x78, 0xDA } }); + + auto const decompressed = TRY_OR_FAIL(Compress::ZlibDecompressor::decompress_all(freshly_pressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(zlib_round_trip_simple_fastest) +{ + u8 const uncompressed[] = "This is a simple text file :)"; + + auto const freshly_pressed = TRY_OR_FAIL(Compress::ZlibCompressor::compress_all({ uncompressed, sizeof(uncompressed) - 1 }, Compress::GenericZlibCompressionLevel::Fastest)); + EXPECT(freshly_pressed.span().slice(0, 2) == ReadonlyBytes { { 0x78, 0x01 } }); + + auto const decompressed = TRY_OR_FAIL(Compress::ZlibDecompressor::decompress_all(freshly_pressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(zlib_decompress_with_missing_end_bits) { // This test case has been extracted from compressed PNG data of `/res/icons/16x16/app-masterword.png`. // The decompression results have been confirmed using the `zlib-flate` tool. - // Note: It is unconfirmed whether there are actually bits missing. - // However, our decompressor implementation ends up in a weird state nonetheless. Array const compressed { 0x08, 0xD7, 0x63, 0x30, 0x86, 0x00, 0x01, 0x06, 0x23, 0x25, 0x30, 0x00, @@ -95,8 +105,6 @@ TEST_CASE(zlib_decompress_with_missing_end_bits) 0x44, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - auto stream = make(compressed); - auto decompressor = TRY_OR_FAIL(Compress::ZlibDecompressor::create(move(stream))); - auto decompressed = TRY_OR_FAIL(decompressor->read_until_eof()); + auto decompressed = TRY_OR_FAIL(Compress::ZlibDecompressor::decompress_all(compressed)); EXPECT_EQ(decompressed.span(), uncompressed.span()); } From 2baa7977a49311c2a5398b2e46aaacecd06875b5 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:38:52 +0100 Subject: [PATCH 062/141] LibCompress: Refactor gzip de/compressor using zlib --- Libraries/LibCompress/Gzip.cpp | 254 +----------------- Libraries/LibCompress/Gzip.h | 100 ++----- .../Compression/DecompressionStream.cpp | 2 +- Tests/LibCompress/TestGzip.cpp | 21 +- 4 files changed, 39 insertions(+), 338 deletions(-) diff --git a/Libraries/LibCompress/Gzip.cpp b/Libraries/LibCompress/Gzip.cpp index 29fea7edb32..1e34af83800 100644 --- a/Libraries/LibCompress/Gzip.cpp +++ b/Libraries/LibCompress/Gzip.cpp @@ -1,267 +1,39 @@ /* * Copyright (c) 2020-2022, the SerenityOS developers. * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ #include -#include -#include -#include -#include +#include namespace Compress { -bool GzipDecompressor::is_likely_compressed(ReadonlyBytes bytes) +ErrorOr> GzipDecompressor::create(MaybeOwned stream) { - return bytes.size() >= 2 && bytes[0] == gzip_magic_1 && bytes[1] == gzip_magic_2; -} - -bool BlockHeader::valid_magic_number() const -{ - return identification_1 == gzip_magic_1 && identification_2 == gzip_magic_2; -} - -bool BlockHeader::supported_by_implementation() const -{ - if (compression_method != 0x08) { - // RFC 1952 does not define any compression methods other than deflate. - return false; - } - - if (flags > Flags::MAX) { - // RFC 1952 does not define any more flags. - return false; - } - - return true; -} - -ErrorOr> GzipDecompressor::Member::construct(BlockHeader header, LittleEndianInputBitStream& stream) -{ - auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned(stream))); - return TRY(adopt_nonnull_own_or_enomem(new (nothrow) Member(header, move(deflate_stream)))); -} - -GzipDecompressor::Member::Member(BlockHeader header, NonnullOwnPtr stream) - : m_header(header) - , m_stream(move(stream)) -{ -} - -GzipDecompressor::GzipDecompressor(MaybeOwned stream) - : m_input_stream(make(move(stream))) -{ -} - -GzipDecompressor::~GzipDecompressor() -{ - m_current_member.clear(); -} - -ErrorOr GzipDecompressor::read_some(Bytes bytes) -{ - size_t total_read = 0; - while (total_read < bytes.size()) { - if (is_eof()) - break; - - auto slice = bytes.slice(total_read); - - if (m_current_member) { - auto current_slice = TRY(current_member().m_stream->read_some(slice)); - current_member().m_checksum.update(current_slice); - current_member().m_nread += current_slice.size(); - - if (current_slice.size() < slice.size()) { - u32 crc32 = TRY(m_input_stream->read_value>()); - u32 input_size = TRY(m_input_stream->read_value>()); - - if (crc32 != current_member().m_checksum.digest()) - return Error::from_string_literal("Stored CRC32 does not match the calculated CRC32 of the current member"); - - if (input_size != current_member().m_nread) - return Error::from_string_literal("Input size does not match the number of read bytes"); - - m_current_member.clear(); - - total_read += current_slice.size(); - continue; - } - - total_read += current_slice.size(); - continue; - } else { - auto current_partial_header_slice = Bytes { m_partial_header, sizeof(BlockHeader) }.slice(m_partial_header_offset); - auto current_partial_header_data = TRY(m_input_stream->read_some(current_partial_header_slice)); - m_partial_header_offset += current_partial_header_data.size(); - - if (is_eof()) - break; - - if (m_partial_header_offset < sizeof(BlockHeader)) { - break; // partial header read - } - m_partial_header_offset = 0; - - BlockHeader header = *(reinterpret_cast(m_partial_header)); - - if (!header.valid_magic_number()) - return Error::from_string_literal("Header does not have a valid magic number"); - - if (!header.supported_by_implementation()) - return Error::from_string_literal("Header is not supported by implementation"); - - if (header.flags & Flags::FEXTRA) { - u16 subfield_id = TRY(m_input_stream->read_value>()); - u16 length = TRY(m_input_stream->read_value>()); - TRY(m_input_stream->discard(length)); - (void)subfield_id; - } - - auto discard_string = [&]() -> ErrorOr { - char next_char; - do { - next_char = TRY(m_input_stream->read_value()); - } while (next_char); - - return {}; - }; - - if (header.flags & Flags::FNAME) - TRY(discard_string()); - - if (header.flags & Flags::FCOMMENT) - TRY(discard_string()); - - if (header.flags & Flags::FHCRC) { - u16 crc = TRY(m_input_stream->read_value>()); - // FIXME: we should probably verify this instead of just assuming it matches - (void)crc; - } - - m_current_member = TRY(Member::construct(header, *m_input_stream)); - continue; - } - } - return bytes.slice(0, total_read); -} - -ErrorOr> GzipDecompressor::describe_header(ReadonlyBytes bytes) -{ - if (bytes.size() < sizeof(BlockHeader)) - return OptionalNone {}; - - auto& header = *(reinterpret_cast(bytes.data())); - if (!header.valid_magic_number() || !header.supported_by_implementation()) - return OptionalNone {}; - - LittleEndian original_size = *reinterpret_cast(bytes.offset(bytes.size() - sizeof(u32))); - return TRY(String::formatted("last modified: {}, original size {}", Core::DateTime::from_timestamp(header.modification_time), (u32)original_size)); + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibDecompressor::new_z_stream(MAX_WBITS | 16)); + return adopt_nonnull_own_or_enomem(new (nothrow) GzipDecompressor(move(buffer), move(stream), zstream)); } ErrorOr GzipDecompressor::decompress_all(ReadonlyBytes bytes) { - auto memory_stream = TRY(try_make(bytes)); - auto gzip_stream = make(move(memory_stream)); - AllocatingMemoryStream output_stream; - - auto buffer = TRY(ByteBuffer::create_uninitialized(4096)); - while (!gzip_stream->is_eof()) { - auto const data = TRY(gzip_stream->read_some(buffer)); - TRY(output_stream.write_until_depleted(data)); - } - - auto output_buffer = TRY(ByteBuffer::create_uninitialized(output_stream.used_buffer_size())); - TRY(output_stream.read_until_filled(output_buffer)); - return output_buffer; + return ::Compress::decompress_all(bytes); } -bool GzipDecompressor::is_eof() const { return m_input_stream->is_eof(); } - -ErrorOr GzipDecompressor::write_some(ReadonlyBytes) +ErrorOr> GzipCompressor::create(MaybeOwned stream, GenericZlibCompressionLevel compression_level) { - return Error::from_errno(EBADF); + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibCompressor::new_z_stream(MAX_WBITS | 16, compression_level)); + return adopt_nonnull_own_or_enomem(new (nothrow) GzipCompressor(move(buffer), move(stream), zstream)); } -ErrorOr> GzipCompressor::create(MaybeOwned output_stream) +ErrorOr GzipCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level) { - BlockHeader header; - header.identification_1 = 0x1f; - header.identification_2 = 0x8b; - header.compression_method = 0x08; - header.flags = 0; - header.modification_time = 0; - header.extra_flags = 3; // DEFLATE sets 2 for maximum compression and 4 for minimum compression - header.operating_system = 3; // unix - TRY(output_stream->write_until_depleted({ &header, sizeof(header) })); - - auto deflate_compressor = TRY(DeflateCompressor::construct(MaybeOwned(*output_stream))); - return adopt_own(*new GzipCompressor { move(output_stream), move(deflate_compressor) }); -} - -GzipCompressor::GzipCompressor(MaybeOwned output_stream, NonnullOwnPtr deflate_compressor) - : m_output_stream(move(output_stream)) - , m_deflate_compressor(move(deflate_compressor)) -{ -} - -ErrorOr GzipCompressor::read_some(Bytes) -{ - return Error::from_errno(EBADF); -} - -ErrorOr GzipCompressor::write_some(ReadonlyBytes bytes) -{ - VERIFY(!m_finished); - - TRY(m_deflate_compressor->write_until_depleted(bytes)); - m_total_bytes += bytes.size(); - m_crc32.update(bytes); - - return bytes.size(); -} - -ErrorOr GzipCompressor::finish() -{ - VERIFY(!m_finished); - m_finished = true; - - TRY(m_deflate_compressor->final_flush()); - TRY(m_output_stream->write_value>(m_crc32.digest())); - TRY(m_output_stream->write_value>(m_total_bytes)); - - return {}; -} - -bool GzipCompressor::is_eof() const -{ - return true; -} - -bool GzipCompressor::is_open() const -{ - return m_output_stream->is_open(); -} - -void GzipCompressor::close() -{ -} - -ErrorOr GzipCompressor::compress_all(ReadonlyBytes bytes) -{ - auto output_stream = TRY(try_make()); - auto gzip_stream = TRY(GzipCompressor::create(MaybeOwned { *output_stream })); - - TRY(gzip_stream->write_until_depleted(bytes)); - TRY(gzip_stream->finish()); - - auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size())); - TRY(output_stream->read_until_filled(buffer.bytes())); - - return buffer; + return ::Compress::compress_all(bytes, compression_level); } } diff --git a/Libraries/LibCompress/Gzip.h b/Libraries/LibCompress/Gzip.h index 736a077fec8..c772620b782 100644 --- a/Libraries/LibCompress/Gzip.h +++ b/Libraries/LibCompress/Gzip.h @@ -1,109 +1,41 @@ /* * Copyright (c) 2020-2022, the SerenityOS developers. * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include -#include -#include -#include -#include +#include namespace Compress { -constexpr u8 gzip_magic_1 = 0x1f; -constexpr u8 gzip_magic_2 = 0x8b; -struct [[gnu::packed]] BlockHeader { - u8 identification_1; - u8 identification_2; - u8 compression_method; - u8 flags; - LittleEndian modification_time; - u8 extra_flags; - u8 operating_system; - - bool valid_magic_number() const; - bool supported_by_implementation() const; -}; - -struct Flags { - static constexpr u8 FTEXT = 1 << 0; - static constexpr u8 FHCRC = 1 << 1; - static constexpr u8 FEXTRA = 1 << 2; - static constexpr u8 FNAME = 1 << 3; - static constexpr u8 FCOMMENT = 1 << 4; - - static constexpr u8 MAX = FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT; -}; - -class GzipDecompressor final : public Stream { +class GzipDecompressor final : public GenericZlibDecompressor { public: - GzipDecompressor(MaybeOwned); - ~GzipDecompressor(); - - virtual ErrorOr read_some(Bytes) override; - virtual ErrorOr write_some(ReadonlyBytes) override; - virtual bool is_eof() const override; - virtual bool is_open() const override { return true; } - virtual void close() override { } + bool is_likely_compressed(ReadonlyBytes bytes); + static ErrorOr> create(MaybeOwned); static ErrorOr decompress_all(ReadonlyBytes); - static ErrorOr> describe_header(ReadonlyBytes); - static bool is_likely_compressed(ReadonlyBytes bytes); - private: - class Member { - public: - static ErrorOr> construct(BlockHeader header, LittleEndianInputBitStream&); - - BlockHeader m_header; - NonnullOwnPtr m_stream; - Crypto::Checksum::CRC32 m_checksum; - size_t m_nread { 0 }; - - private: - Member(BlockHeader, NonnullOwnPtr); - }; - - Member const& current_member() const { return *m_current_member; } - Member& current_member() { return *m_current_member; } - - NonnullOwnPtr m_input_stream; - u8 m_partial_header[sizeof(BlockHeader)]; - size_t m_partial_header_offset { 0 }; - OwnPtr m_current_member {}; - - bool m_eof { false }; + GzipDecompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibDecompressor(move(buffer), move(stream), zstream) + { + } }; -class GzipCompressor final : public Stream { +class GzipCompressor final : public GenericZlibCompressor { public: - static ErrorOr> create(MaybeOwned); - - virtual ErrorOr read_some(Bytes) override; - virtual ErrorOr write_some(ReadonlyBytes) override; - virtual bool is_eof() const override; - virtual bool is_open() const override; - virtual void close() override; - - static ErrorOr compress_all(ReadonlyBytes bytes); - - ErrorOr finish(); + static ErrorOr> create(MaybeOwned, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); + static ErrorOr compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); private: - GzipCompressor(MaybeOwned, NonnullOwnPtr); - - MaybeOwned m_output_stream; - NonnullOwnPtr m_deflate_compressor; - - Crypto::Checksum::CRC32 m_crc32; - size_t m_total_bytes { 0 }; - bool m_finished { false }; + GzipCompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibCompressor(move(buffer), move(stream), zstream) + { + } }; } diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index 1a114527559..392a1f47920 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -36,7 +36,7 @@ WebIDL::ExceptionOr> DecompressionStream::construct case Bindings::CompressionFormat::DeflateRaw: return TRY(Compress::DeflateDecompressor::construct(make(move(input_stream)))); case Bindings::CompressionFormat::Gzip: - return make(move(input_stream)); + return TRY(Compress::GzipDecompressor::create((move(input_stream)))); } VERIFY_NOT_REACHED(); diff --git a/Tests/LibCompress/TestGzip.cpp b/Tests/LibCompress/TestGzip.cpp index dd787dc4540..5a76b2cee42 100644 --- a/Tests/LibCompress/TestGzip.cpp +++ b/Tests/LibCompress/TestGzip.cpp @@ -4,11 +4,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include - -#include -#include #include +#include TEST_CASE(gzip_decompress_simple) { @@ -20,8 +17,8 @@ TEST_CASE(gzip_decompress_simple) u8 const uncompressed[] = "word1 abc word2"; - auto const decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::GzipDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(gzip_decompress_multiple_members) @@ -36,8 +33,8 @@ TEST_CASE(gzip_decompress_multiple_members) u8 const uncompressed[] = "abcabcabcabc"; - auto const decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::GzipDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(gzip_decompress_zeroes) @@ -61,8 +58,8 @@ TEST_CASE(gzip_decompress_zeroes) Array const uncompressed = { 0 }; - auto const decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); + auto const decompressed = TRY_OR_FAIL(Compress::GzipDecompressor::decompress_all(compressed)); + EXPECT(uncompressed == decompressed.bytes()); } TEST_CASE(gzip_decompress_repeat_around_buffer) @@ -81,8 +78,8 @@ TEST_CASE(gzip_decompress_repeat_around_buffer) uncompressed.span().slice(0x0100, 0x7e00).fill(0); uncompressed.span().slice(0x7f00, 0x0100).fill(1); - auto const decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); + auto const decompressed = TRY_OR_FAIL(Compress::GzipDecompressor::decompress_all(compressed)); + EXPECT(uncompressed == decompressed.bytes()); } TEST_CASE(gzip_round_trip) From 1c2b373e9ccc40782dcfa3c86f9336d00aeeef59 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:41:22 +0100 Subject: [PATCH 063/141] LibCompress: Refactor deflate de/compressor using zlib Also remove two crash tests that are not relevant anymore because the implementation changed substantially. --- Libraries/LibCompress/Deflate.cpp | 815 +----------------- Libraries/LibCompress/Deflate.h | 179 +--- .../ImageFormats/WebPWriterLossless.cpp | 1 + .../LibWeb/Compression/CompressionStream.cpp | 15 +- .../Compression/DecompressionStream.cpp | 2 +- Tests/LibCompress/TestDeflate.cpp | 61 +- ...zzDeflateCompression-6163230961303552.fuzz | Bin 29687 -> 0 bytes ...DeflateDecompression-5523852259360768.fuzz | 1 - 8 files changed, 56 insertions(+), 1018 deletions(-) delete mode 100644 Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz delete mode 100644 Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz diff --git a/Libraries/LibCompress/Deflate.cpp b/Libraries/LibCompress/Deflate.cpp index c229c58ce9f..dece3cc77b8 100644 --- a/Libraries/LibCompress/Deflate.cpp +++ b/Libraries/LibCompress/Deflate.cpp @@ -1,25 +1,19 @@ /* * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include #include -#include #include -#include +#include + +#include namespace Compress { -static constexpr u8 deflate_special_code_length_copy = 16; -static constexpr u8 deflate_special_code_length_zeros = 17; -static constexpr u8 deflate_special_code_length_long_zeros = 18; - -static constexpr int EndOfBlock = 256; - CanonicalCode const& CanonicalCode::fixed_literal_codes() { static CanonicalCode code; @@ -165,807 +159,28 @@ ErrorOr CanonicalCode::read_symbol(LittleEndianInputBitStream& stream) cons return Error::from_string_literal("Symbol exceeds maximum symbol number"); } -DeflateDecompressor::CompressedBlock::CompressedBlock(DeflateDecompressor& decompressor, CanonicalCode literal_codes, Optional distance_codes) - : m_decompressor(decompressor) - , m_literal_codes(literal_codes) - , m_distance_codes(distance_codes) -{ -} - -ErrorOr DeflateDecompressor::CompressedBlock::try_read_more() -{ - if (m_eof == true) - return false; - - auto const symbol = TRY(m_literal_codes.read_symbol(*m_decompressor.m_input_stream)); - - if (symbol >= 286) - return Error::from_string_literal("Invalid deflate literal/length symbol"); - - if (symbol < EndOfBlock) { - u8 byte_symbol = symbol; - m_decompressor.m_output_buffer.write({ &byte_symbol, sizeof(byte_symbol) }); - return true; - } - - if (symbol == EndOfBlock) { - m_eof = true; - return false; - } - - if (!m_distance_codes.has_value()) - return Error::from_string_literal("Distance codes have not been initialized"); - - auto const length = TRY(m_decompressor.decode_length(symbol)); - auto const distance_symbol = TRY(m_distance_codes.value().read_symbol(*m_decompressor.m_input_stream)); - if (distance_symbol >= 30) - return Error::from_string_literal("Invalid deflate distance symbol"); - - auto const distance = TRY(m_decompressor.decode_distance(distance_symbol)); - - auto copied_length = TRY(m_decompressor.m_output_buffer.copy_from_seekback(distance, length)); - - // TODO: What should we do if the output buffer is full? - VERIFY(copied_length == length); - - return true; -} - -DeflateDecompressor::UncompressedBlock::UncompressedBlock(DeflateDecompressor& decompressor, size_t length) - : m_decompressor(decompressor) - , m_bytes_remaining(length) -{ -} - -ErrorOr DeflateDecompressor::UncompressedBlock::try_read_more() -{ - if (m_bytes_remaining == 0) - return false; - - if (m_decompressor.m_input_stream->is_eof()) - return Error::from_string_literal("Input data ends in the middle of an uncompressed DEFLATE block"); - - Array temporary_buffer; - auto readable_bytes = temporary_buffer.span().trim(min(m_bytes_remaining, m_decompressor.m_output_buffer.empty_space())); - auto read_bytes = TRY(m_decompressor.m_input_stream->read_some(readable_bytes)); - auto written_bytes = m_decompressor.m_output_buffer.write(read_bytes); - VERIFY(read_bytes.size() == written_bytes); - - m_bytes_remaining -= written_bytes; - return true; -} - -ErrorOr> DeflateDecompressor::construct(MaybeOwned stream) -{ - auto output_buffer = TRY(CircularBuffer::create_empty(32 * KiB)); - return TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(stream), move(output_buffer)))); -} - -DeflateDecompressor::DeflateDecompressor(MaybeOwned stream, CircularBuffer output_buffer) - : m_input_stream(move(stream)) - , m_output_buffer(move(output_buffer)) -{ -} - -DeflateDecompressor::~DeflateDecompressor() -{ - if (m_state == State::ReadingCompressedBlock) - m_compressed_block.~CompressedBlock(); - if (m_state == State::ReadingUncompressedBlock) - m_uncompressed_block.~UncompressedBlock(); -} - -ErrorOr DeflateDecompressor::read_some(Bytes bytes) -{ - size_t total_read = 0; - while (total_read < bytes.size()) { - auto slice = bytes.slice(total_read); - - if (m_state == State::Idle) { - if (m_read_final_block) - break; - - m_read_final_block = TRY(m_input_stream->read_bit()); - auto const block_type = TRY(m_input_stream->read_bits(2)); - - if (block_type == 0b00) { - m_input_stream->align_to_byte_boundary(); - - u16 length = TRY(m_input_stream->read_value>()); - u16 negated_length = TRY(m_input_stream->read_value>()); - - if ((length ^ 0xffff) != negated_length) - return Error::from_string_literal("Calculated negated length does not equal stored negated length"); - - m_state = State::ReadingUncompressedBlock; - new (&m_uncompressed_block) UncompressedBlock(*this, length); - - continue; - } - - if (block_type == 0b01) { - m_state = State::ReadingCompressedBlock; - new (&m_compressed_block) CompressedBlock(*this, CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes()); - - continue; - } - - if (block_type == 0b10) { - CanonicalCode literal_codes; - Optional distance_codes; - TRY(decode_codes(literal_codes, distance_codes)); - - m_state = State::ReadingCompressedBlock; - new (&m_compressed_block) CompressedBlock(*this, literal_codes, distance_codes); - - continue; - } - - return Error::from_string_literal("Unhandled block type for Idle state"); - } - - if (m_state == State::ReadingCompressedBlock) { - auto nread = m_output_buffer.read(slice).size(); - - while (nread < slice.size() && TRY(m_compressed_block.try_read_more())) { - nread += m_output_buffer.read(slice.slice(nread)).size(); - } - - total_read += nread; - if (nread == slice.size()) - break; - - m_compressed_block.~CompressedBlock(); - m_state = State::Idle; - - continue; - } - - if (m_state == State::ReadingUncompressedBlock) { - auto nread = m_output_buffer.read(slice).size(); - - while (nread < slice.size() && TRY(m_uncompressed_block.try_read_more())) { - nread += m_output_buffer.read(slice.slice(nread)).size(); - } - - total_read += nread; - if (nread == slice.size()) - break; - - m_uncompressed_block.~UncompressedBlock(); - m_state = State::Idle; - - continue; - } - - VERIFY_NOT_REACHED(); - } - - return bytes.slice(0, total_read); -} - -bool DeflateDecompressor::is_eof() const { return m_state == State::Idle && m_read_final_block; } - -ErrorOr DeflateDecompressor::write_some(ReadonlyBytes) -{ - return Error::from_errno(EBADF); -} - -bool DeflateDecompressor::is_open() const -{ - return true; -} - -void DeflateDecompressor::close() +ErrorOr> DeflateDecompressor::create(MaybeOwned stream) { + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibDecompressor::new_z_stream(-MAX_WBITS)); + return adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(buffer), move(stream), zstream)); } ErrorOr DeflateDecompressor::decompress_all(ReadonlyBytes bytes) { - FixedMemoryStream memory_stream { bytes }; - LittleEndianInputBitStream bit_stream { MaybeOwned(memory_stream) }; - auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned(bit_stream))); - return deflate_stream->read_until_eof(4096); + return ::Compress::decompress_all(bytes); } -ErrorOr DeflateDecompressor::decode_length(u32 symbol) +ErrorOr> DeflateCompressor::create(MaybeOwned stream, GenericZlibCompressionLevel compression_level) { - if (symbol <= 264) - return symbol - 254; - - if (symbol <= 284) { - auto extra_bits = (symbol - 261) / 4; - return (((symbol - 265) % 4 + 4) << extra_bits) + 3 + TRY(m_input_stream->read_bits(extra_bits)); - } - - if (symbol == 285) - return DeflateDecompressor::max_back_reference_length; - - VERIFY_NOT_REACHED(); + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibCompressor::new_z_stream(-MAX_WBITS, compression_level)); + return adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(buffer), move(stream), zstream)); } -ErrorOr DeflateDecompressor::decode_distance(u32 symbol) +ErrorOr DeflateCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level) { - if (symbol <= 3) - return symbol + 1; - - if (symbol <= 29) { - auto extra_bits = (symbol / 2) - 1; - return ((symbol % 2 + 2) << extra_bits) + 1 + TRY(m_input_stream->read_bits(extra_bits)); - } - - VERIFY_NOT_REACHED(); -} - -ErrorOr DeflateDecompressor::decode_codes(CanonicalCode& literal_code, Optional& distance_code) -{ - auto literal_code_count = TRY(m_input_stream->read_bits(5)) + 257; - auto distance_code_count = TRY(m_input_stream->read_bits(5)) + 1; - auto code_length_count = TRY(m_input_stream->read_bits(4)) + 4; - - // First we have to extract the code lengths of the code that was used to encode the code lengths of - // the code that was used to encode the block. - - u8 code_lengths_code_lengths[19] = { 0 }; - for (size_t i = 0; i < code_length_count; ++i) { - code_lengths_code_lengths[code_lengths_code_lengths_order[i]] = TRY(m_input_stream->read_bits(3)); - } - - // Now we can extract the code that was used to encode the code lengths of the code that was used to - // encode the block. - auto const code_length_code = TRY(CanonicalCode::from_bytes({ code_lengths_code_lengths, sizeof(code_lengths_code_lengths) })); - - // Next we extract the code lengths of the code that was used to encode the block. - Vector code_lengths; - while (code_lengths.size() < literal_code_count + distance_code_count) { - auto symbol = TRY(code_length_code.read_symbol(*m_input_stream)); - - if (symbol < deflate_special_code_length_copy) { - code_lengths.append(static_cast(symbol)); - } else if (symbol == deflate_special_code_length_copy) { - if (code_lengths.is_empty()) - return Error::from_string_literal("Found no codes to copy before a copy block"); - auto nrepeat = 3 + TRY(m_input_stream->read_bits(2)); - for (size_t j = 0; j < nrepeat; ++j) - code_lengths.append(code_lengths.last()); - } else if (symbol == deflate_special_code_length_zeros) { - auto nrepeat = 3 + TRY(m_input_stream->read_bits(3)); - for (size_t j = 0; j < nrepeat; ++j) - code_lengths.append(0); - } else { - VERIFY(symbol == deflate_special_code_length_long_zeros); - auto nrepeat = 11 + TRY(m_input_stream->read_bits(7)); - for (size_t j = 0; j < nrepeat; ++j) - code_lengths.append(0); - } - } - - if (code_lengths.size() != literal_code_count + distance_code_count) - return Error::from_string_literal("Number of code lengths does not match the sum of codes"); - - // Now we extract the code that was used to encode literals and lengths in the block. - literal_code = TRY(CanonicalCode::from_bytes(code_lengths.span().trim(literal_code_count))); - - // Now we extract the code that was used to encode distances in the block. - - if (distance_code_count == 1) { - auto length = code_lengths[literal_code_count]; - - if (length == 0) - return {}; - else if (length != 1) - return Error::from_string_literal("Length for a single distance code is longer than 1"); - } - - distance_code = TRY(CanonicalCode::from_bytes(code_lengths.span().slice(literal_code_count))); - - return {}; -} - -ErrorOr> DeflateCompressor::construct(MaybeOwned stream, CompressionLevel compression_level) -{ - auto bit_stream = TRY(try_make(move(stream))); - auto deflate_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(bit_stream), compression_level))); - return deflate_compressor; -} - -DeflateCompressor::DeflateCompressor(NonnullOwnPtr stream, CompressionLevel compression_level) - : m_compression_level(compression_level) - , m_compression_constants(compression_constants[static_cast(m_compression_level)]) - , m_output_stream(move(stream)) -{ - m_symbol_frequencies.fill(0); - m_distance_frequencies.fill(0); -} - -DeflateCompressor::~DeflateCompressor() = default; - -ErrorOr DeflateCompressor::read_some(Bytes) -{ - return Error::from_errno(EBADF); -} - -ErrorOr DeflateCompressor::write_some(ReadonlyBytes bytes) -{ - VERIFY(!m_finished); - - size_t total_written = 0; - while (!bytes.is_empty()) { - auto n_written = bytes.copy_trimmed_to(pending_block().slice(m_pending_block_size)); - m_pending_block_size += n_written; - - if (m_pending_block_size == block_size) - TRY(flush()); - - bytes = bytes.slice(n_written); - total_written += n_written; - } - return total_written; -} - -bool DeflateCompressor::is_eof() const -{ - return true; -} - -bool DeflateCompressor::is_open() const -{ - return m_output_stream->is_open(); -} - -void DeflateCompressor::close() -{ -} - -// Knuth's multiplicative hash on 4 bytes -u16 DeflateCompressor::hash_sequence(u8 const* bytes) -{ - constexpr u32 const knuth_constant = 2654435761; // shares no common factors with 2^32 - return ((bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24) * knuth_constant) >> (32 - hash_bits); -} - -size_t DeflateCompressor::compare_match_candidate(size_t start, size_t candidate, size_t previous_match_length, size_t maximum_match_length) -{ - VERIFY(previous_match_length < maximum_match_length); - - // We firstly check that the match is at least (prev_match_length + 1) long, we check backwards as there's a higher chance the end mismatches - for (ssize_t i = previous_match_length; i >= 0; i--) { - if (m_rolling_window[start + i] != m_rolling_window[candidate + i]) - return 0; - } - - // Find the actual length - auto match_length = previous_match_length + 1; - while (match_length < maximum_match_length && m_rolling_window[start + match_length] == m_rolling_window[candidate + match_length]) { - match_length++; - } - - VERIFY(match_length > previous_match_length); - VERIFY(match_length <= maximum_match_length); - return match_length; -} - -size_t DeflateCompressor::find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t maximum_match_length, size_t& match_position) -{ - auto max_chain_length = m_compression_constants.max_chain; - if (previous_match_length == 0) - previous_match_length = min_match_length - 1; // we only care about matches that are at least min_match_length long - if (previous_match_length >= maximum_match_length) - return 0; // we can't improve a maximum length match - if (previous_match_length >= m_compression_constants.max_lazy_length) - return 0; // the previous match is already pretty, we shouldn't waste another full search - if (previous_match_length >= m_compression_constants.good_match_length) - max_chain_length /= 4; // we already have a pretty good much, so do a shorter search - - auto candidate = m_hash_head[hash]; - auto match_found = false; - while (max_chain_length--) { - if (candidate == empty_slot) - break; // no remaining candidates - - VERIFY(candidate < start); - if (start - candidate > window_size) - break; // outside the window - - auto match_length = compare_match_candidate(start, candidate, previous_match_length, maximum_match_length); - - if (match_length != 0) { - match_found = true; - match_position = candidate; - previous_match_length = match_length; - - if (match_length == maximum_match_length) - return match_length; // bail if we got the maximum possible length - } - - candidate = m_hash_prev[candidate % window_size]; - } - if (!match_found) - return 0; // we didn't find any matches - return previous_match_length; // we found matches, but they were at most previous_match_length long -} - -ALWAYS_INLINE u8 DeflateCompressor::distance_to_base(u16 distance) -{ - return (distance <= 256) ? distance_to_base_lo[distance - 1] : distance_to_base_hi[(distance - 1) >> 7]; -} - -void DeflateCompressor::lz77_compress_block() -{ - for (auto& slot : m_hash_head) { // initialize chained hash table - slot = empty_slot; - } - - auto insert_hash = [&](auto pos, auto hash) { - auto window_pos = pos % window_size; - m_hash_prev[window_pos] = m_hash_head[hash]; - m_hash_head[hash] = window_pos; - }; - - auto emit_literal = [&](auto literal) { - VERIFY(m_pending_symbol_size <= block_size + 1); - auto index = m_pending_symbol_size++; - m_symbol_buffer[index].distance = 0; - m_symbol_buffer[index].literal = literal; - m_symbol_frequencies[literal]++; - }; - - auto emit_back_reference = [&](auto distance, auto length) { - VERIFY(m_pending_symbol_size <= block_size + 1); - auto index = m_pending_symbol_size++; - m_symbol_buffer[index].distance = distance; - m_symbol_buffer[index].length = length; - m_symbol_frequencies[length_to_symbol[length]]++; - m_distance_frequencies[distance_to_base(distance)]++; - }; - - size_t previous_match_length = 0; - size_t previous_match_position = 0; - - VERIFY(m_compression_constants.great_match_length <= max_match_length); - - // our block starts at block_size and is m_pending_block_size in length - auto block_end = block_size + m_pending_block_size; - size_t current_position; - for (current_position = block_size; current_position < block_end - min_match_length + 1; current_position++) { - auto hash = hash_sequence(&m_rolling_window[current_position]); - size_t match_position; - auto match_length = find_back_match(current_position, hash, previous_match_length, - min(m_compression_constants.great_match_length, block_end - current_position), match_position); - - insert_hash(current_position, hash); - - // if the previous match is as good as the new match, just use it - if (previous_match_length != 0 && previous_match_length >= match_length) { - emit_back_reference((current_position - 1) - previous_match_position, previous_match_length); - - // skip all the bytes that are included in this match - for (size_t j = current_position + 1; j < min(current_position - 1 + previous_match_length, block_end - min_match_length + 1); j++) { - insert_hash(j, hash_sequence(&m_rolling_window[j])); - } - current_position = (current_position - 1) + previous_match_length - 1; - previous_match_length = 0; - continue; - } - - if (match_length == 0) { - VERIFY(previous_match_length == 0); - emit_literal(m_rolling_window[current_position]); - continue; - } - - // if this is a lazy match, and the new match is better than the old one, output previous as literal - if (previous_match_length != 0) { - emit_literal(m_rolling_window[current_position - 1]); - } - - previous_match_length = match_length; - previous_match_position = match_position; - } - - // clean up leftover lazy match - if (previous_match_length != 0) { - emit_back_reference((current_position - 1) - previous_match_position, previous_match_length); - current_position = (current_position - 1) + previous_match_length; - } - - // output remaining literals - while (current_position < block_end) { - emit_literal(m_rolling_window[current_position++]); - } -} - -size_t DeflateCompressor::huffman_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths) -{ - size_t length = 0; - - for (size_t i = 0; i < 286; i++) { - auto frequency = m_symbol_frequencies[i]; - length += literal_bit_lengths[i] * frequency; - - if (i >= 257) // back reference length symbols - length += packed_length_symbols[i - 257].extra_bits * frequency; - } - - for (size_t i = 0; i < 30; i++) { - auto frequency = m_distance_frequencies[i]; - length += distance_bit_lengths[i] * frequency; - length += packed_distances[i].extra_bits * frequency; - } - - return length; -} - -size_t DeflateCompressor::uncompressed_block_length() -{ - auto padding = 8 - ((m_output_stream->bit_offset() + 3) % 8); - // 3 bit block header + align to byte + 2 * 16 bit length fields + block contents - return 3 + padding + (2 * 16) + m_pending_block_size * 8; -} - -size_t DeflateCompressor::fixed_block_length() -{ - // block header + fixed huffman encoded block contents - return 3 + huffman_block_length(fixed_literal_bit_lengths, fixed_distance_bit_lengths); -} - -size_t DeflateCompressor::dynamic_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array const& code_lengths_bit_lengths, Array const& code_lengths_frequencies, size_t code_lengths_count) -{ - // block header + literal code count + distance code count + code length count - auto length = 3 + 5 + 5 + 4; - - // 3 bits per code_length - length += 3 * code_lengths_count; - - for (size_t i = 0; i < code_lengths_frequencies.size(); i++) { - auto frequency = code_lengths_frequencies[i]; - length += code_lengths_bit_lengths[i] * frequency; - - if (i == deflate_special_code_length_copy) { - length += 2 * frequency; - } else if (i == deflate_special_code_length_zeros) { - length += 3 * frequency; - } else if (i == deflate_special_code_length_long_zeros) { - length += 7 * frequency; - } - } - - return length + huffman_block_length(literal_bit_lengths, distance_bit_lengths); -} - -ErrorOr DeflateCompressor::write_huffman(CanonicalCode const& literal_code, Optional const& distance_code) -{ - auto has_distances = distance_code.has_value(); - for (size_t i = 0; i < m_pending_symbol_size; i++) { - if (m_symbol_buffer[i].distance == 0) { - TRY(literal_code.write_symbol(*m_output_stream, m_symbol_buffer[i].literal)); - continue; - } - VERIFY(has_distances); - auto symbol = length_to_symbol[m_symbol_buffer[i].length]; - TRY(literal_code.write_symbol(*m_output_stream, symbol)); - // Emit extra bits if needed - TRY(m_output_stream->write_bits(m_symbol_buffer[i].length - packed_length_symbols[symbol - 257].base_length, packed_length_symbols[symbol - 257].extra_bits)); - - auto base_distance = distance_to_base(m_symbol_buffer[i].distance); - TRY(distance_code.value().write_symbol(*m_output_stream, base_distance)); - // Emit extra bits if needed - TRY(m_output_stream->write_bits(m_symbol_buffer[i].distance - packed_distances[base_distance].base_distance, packed_distances[base_distance].extra_bits)); - } - return {}; -} - -size_t DeflateCompressor::encode_huffman_lengths(ReadonlyBytes lengths, Array& encoded_lengths) -{ - size_t encoded_count = 0; - size_t i = 0; - while (i < lengths.size()) { - if (lengths[i] == 0) { - auto zero_count = 0; - for (size_t j = i; j < min(lengths.size(), i + 138) && lengths[j] == 0; j++) - zero_count++; - - if (zero_count < 3) { // below minimum repeated zero count - encoded_lengths[encoded_count++].symbol = 0; - i++; - continue; - } - - if (zero_count <= 10) { - encoded_lengths[encoded_count].symbol = deflate_special_code_length_zeros; - encoded_lengths[encoded_count++].count = zero_count; - } else { - encoded_lengths[encoded_count].symbol = deflate_special_code_length_long_zeros; - encoded_lengths[encoded_count++].count = zero_count; - } - i += zero_count; - continue; - } - - encoded_lengths[encoded_count++].symbol = lengths[i++]; - - auto copy_count = 0; - for (size_t j = i; j < min(lengths.size(), i + 6) && lengths[j] == lengths[i - 1]; j++) - copy_count++; - - if (copy_count >= 3) { - encoded_lengths[encoded_count].symbol = deflate_special_code_length_copy; - encoded_lengths[encoded_count++].count = copy_count; - i += copy_count; - continue; - } - } - return encoded_count; -} - -size_t DeflateCompressor::encode_block_lengths(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count) -{ - literal_code_count = max_huffman_literals; - distance_code_count = max_huffman_distances; - - VERIFY(literal_bit_lengths[EndOfBlock] != 0); // Make sure at least the EndOfBlock marker is present - while (literal_bit_lengths[literal_code_count - 1] == 0) - literal_code_count--; - - // Drop trailing zero lengths, keeping at least one - while (distance_bit_lengths[distance_code_count - 1] == 0 && distance_code_count > 1) - distance_code_count--; - - Array all_lengths {}; - for (size_t i = 0; i < literal_code_count; i++) - all_lengths[i] = literal_bit_lengths[i]; - for (size_t i = 0; i < distance_code_count; i++) - all_lengths[literal_code_count + i] = distance_bit_lengths[i]; - - return encode_huffman_lengths(all_lengths.span().trim(literal_code_count + distance_code_count), encoded_lengths); -} - -ErrorOr DeflateCompressor::write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional const& distance_code, size_t distance_code_count, Array const& code_lengths_bit_lengths, size_t code_length_count, Array const& encoded_lengths, size_t encoded_lengths_count) -{ - TRY(m_output_stream->write_bits(literal_code_count - 257, 5)); - TRY(m_output_stream->write_bits(distance_code_count - 1, 5)); - TRY(m_output_stream->write_bits(code_length_count - 4, 4)); - - for (size_t i = 0; i < code_length_count; i++) { - TRY(m_output_stream->write_bits(code_lengths_bit_lengths[code_lengths_code_lengths_order[i]], 3)); - } - - auto code_lengths_code = MUST(CanonicalCode::from_bytes(code_lengths_bit_lengths)); - for (size_t i = 0; i < encoded_lengths_count; i++) { - auto encoded_length = encoded_lengths[i]; - TRY(code_lengths_code.write_symbol(*m_output_stream, encoded_length.symbol)); - if (encoded_length.symbol == deflate_special_code_length_copy) { - TRY(m_output_stream->write_bits(encoded_length.count - 3, 2)); - } else if (encoded_length.symbol == deflate_special_code_length_zeros) { - TRY(m_output_stream->write_bits(encoded_length.count - 3, 3)); - } else if (encoded_length.symbol == deflate_special_code_length_long_zeros) { - TRY(m_output_stream->write_bits(encoded_length.count - 11, 7)); - } - } - - TRY(write_huffman(literal_code, distance_code)); - return {}; -} - -ErrorOr DeflateCompressor::flush() -{ - TRY(m_output_stream->write_bits(m_finished, 1)); - - // if this is just an empty block to signify the end of the deflate stream use the smallest block possible (10 bits total) - if (m_pending_block_size == 0) { - VERIFY(m_finished); // we shouldn't be writing empty blocks unless this is the final one - TRY(m_output_stream->write_bits(0b01u, 2)); // fixed huffman codes - TRY(m_output_stream->write_bits(0b0000000u, 7)); // end of block symbol - TRY(m_output_stream->align_to_byte_boundary()); - return {}; - } - - auto write_uncompressed = [&]() -> ErrorOr { - TRY(m_output_stream->write_bits(0b00u, 2)); // no compression - TRY(m_output_stream->align_to_byte_boundary()); - TRY(m_output_stream->write_value>(m_pending_block_size)); - TRY(m_output_stream->write_value>(~m_pending_block_size)); - TRY(m_output_stream->write_until_depleted(pending_block().slice(0, m_pending_block_size))); - return {}; - }; - - if (m_compression_level == CompressionLevel::STORE) { // disabled compression fast path - TRY(write_uncompressed()); - m_pending_block_size = 0; - return {}; - } - - // The following implementation of lz77 compression and huffman encoding is based on the reference implementation by Hans Wennborg https://www.hanshq.net/zip.html - - // this reads from the pending block and writes to m_symbol_buffer - lz77_compress_block(); - - // insert EndOfBlock marker to the symbol buffer - m_symbol_buffer[m_pending_symbol_size].distance = 0; - m_symbol_buffer[m_pending_symbol_size++].literal = EndOfBlock; - m_symbol_frequencies[EndOfBlock]++; - - // generate optimal dynamic huffman code lengths - Array dynamic_literal_bit_lengths {}; - Array dynamic_distance_bit_lengths {}; - generate_huffman_lengths(dynamic_literal_bit_lengths, m_symbol_frequencies, 15); // deflate data huffman can use up to 15 bits per symbol - generate_huffman_lengths(dynamic_distance_bit_lengths, m_distance_frequencies, 15); - - // encode literal and distance lengths together in deflate format - Array encoded_lengths {}; - size_t literal_code_count; - size_t distance_code_count; - auto encoded_lengths_count = encode_block_lengths(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, encoded_lengths, literal_code_count, distance_code_count); - - // count code length frequencies - Array code_lengths_frequencies { 0 }; - for (size_t i = 0; i < encoded_lengths_count; i++) { - code_lengths_frequencies[encoded_lengths[i].symbol]++; - } - // generate optimal huffman code lengths code lengths - Array code_lengths_bit_lengths {}; - generate_huffman_lengths(code_lengths_bit_lengths, code_lengths_frequencies, 7); // deflate code length huffman can use up to 7 bits per symbol - // calculate actual code length code lengths count (without trailing zeros) - auto code_lengths_count = code_lengths_bit_lengths.size(); - while (code_lengths_bit_lengths[code_lengths_code_lengths_order[code_lengths_count - 1]] == 0) - code_lengths_count--; - - auto uncompressed_size = uncompressed_block_length(); - auto fixed_huffman_size = fixed_block_length(); - auto dynamic_huffman_size = dynamic_block_length(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, code_lengths_bit_lengths, code_lengths_frequencies, code_lengths_count); - - // If the compression somehow didn't reduce the size enough, just write out the block uncompressed as it allows for much faster decompression - if (uncompressed_size <= min(fixed_huffman_size, dynamic_huffman_size)) { - TRY(write_uncompressed()); - } else if (fixed_huffman_size <= dynamic_huffman_size) { - // If the fixed and dynamic huffman codes come out the same size, prefer the fixed version, as it takes less time to decode fixed huffman codes. - TRY(m_output_stream->write_bits(0b01u, 2)); - TRY(write_huffman(CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes())); - } else { - // dynamic huffman codes - TRY(m_output_stream->write_bits(0b10u, 2)); - auto literal_code = MUST(CanonicalCode::from_bytes(dynamic_literal_bit_lengths)); - auto distance_code_or_error = CanonicalCode::from_bytes(dynamic_distance_bit_lengths); - Optional distance_code; - if (!distance_code_or_error.is_error()) - distance_code = distance_code_or_error.release_value(); - TRY(write_dynamic_huffman(literal_code, literal_code_count, distance_code, distance_code_count, code_lengths_bit_lengths, code_lengths_count, encoded_lengths, encoded_lengths_count)); - } - if (m_finished) - TRY(m_output_stream->align_to_byte_boundary()); - - // reset all block specific members - m_pending_block_size = 0; - m_pending_symbol_size = 0; - m_symbol_frequencies.fill(0); - m_distance_frequencies.fill(0); - // On the final block this copy will potentially produce an invalid search window, but since its the final block we dont care - pending_block().copy_trimmed_to({ m_rolling_window, block_size }); - - return {}; -} - -ErrorOr DeflateCompressor::final_flush() -{ - VERIFY(!m_finished); - m_finished = true; - TRY(flush()); - TRY(m_output_stream->flush_buffer_to_stream()); - return {}; -} - -ErrorOr DeflateCompressor::compress_all(ReadonlyBytes bytes, CompressionLevel compression_level) -{ - auto output_stream = TRY(try_make()); - auto deflate_stream = TRY(DeflateCompressor::construct(MaybeOwned(*output_stream), compression_level)); - - TRY(deflate_stream->write_until_depleted(bytes)); - TRY(deflate_stream->final_flush()); - - auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size())); - TRY(output_stream->read_until_filled(buffer)); - - return buffer; + return ::Compress::compress_all(bytes, compression_level); } } diff --git a/Libraries/LibCompress/Deflate.h b/Libraries/LibCompress/Deflate.h index 9e470c6de9c..466ff53affd 100644 --- a/Libraries/LibCompress/Deflate.h +++ b/Libraries/LibCompress/Deflate.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,14 +9,8 @@ #pragma once #include -#include -#include -#include -#include -#include #include -#include -#include +#include namespace Compress { @@ -60,172 +55,28 @@ ALWAYS_INLINE ErrorOr CanonicalCode::write_symbol(LittleEndianOutputBitStr return {}; } -class DeflateDecompressor final : public Stream { -private: - class CompressedBlock { - public: - CompressedBlock(DeflateDecompressor&, CanonicalCode literal_codes, Optional distance_codes); - - ErrorOr try_read_more(); - - private: - bool m_eof { false }; - - DeflateDecompressor& m_decompressor; - CanonicalCode m_literal_codes; - Optional m_distance_codes; - }; - - class UncompressedBlock { - public: - UncompressedBlock(DeflateDecompressor&, size_t); - - ErrorOr try_read_more(); - - private: - DeflateDecompressor& m_decompressor; - size_t m_bytes_remaining; - }; - - enum class State { - Idle, - ReadingCompressedBlock, - ReadingUncompressedBlock - }; - +class DeflateDecompressor final : public GenericZlibDecompressor { public: - friend CompressedBlock; - friend UncompressedBlock; - - static ErrorOr> construct(MaybeOwned stream); - ~DeflateDecompressor(); - - virtual ErrorOr read_some(Bytes) override; - virtual ErrorOr write_some(ReadonlyBytes) override; - virtual bool is_eof() const override; - virtual bool is_open() const override; - virtual void close() override; - + static ErrorOr> create(MaybeOwned); static ErrorOr decompress_all(ReadonlyBytes); private: - DeflateDecompressor(MaybeOwned stream, CircularBuffer buffer); - - ErrorOr decode_length(u32); - ErrorOr decode_distance(u32); - ErrorOr decode_codes(CanonicalCode& literal_code, Optional& distance_code); - - static constexpr u16 max_back_reference_length = 258; - - bool m_read_final_block { false }; - - State m_state { State::Idle }; - union { - CompressedBlock m_compressed_block; - UncompressedBlock m_uncompressed_block; - }; - - MaybeOwned m_input_stream; - CircularBuffer m_output_buffer; + DeflateDecompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibDecompressor(move(buffer), move(stream), zstream) + { + } }; -class DeflateCompressor final : public Stream { +class DeflateCompressor final : public GenericZlibCompressor { public: - static constexpr size_t block_size = 32 * KiB - 1; // TODO: this can theoretically be increased to 64 KiB - 2 - static constexpr size_t window_size = block_size * 2; - static constexpr size_t hash_bits = 15; - static constexpr size_t max_huffman_literals = 288; - static constexpr size_t max_huffman_distances = 32; - static constexpr size_t min_match_length = 4; // matches smaller than these are not worth the size of the back reference - static constexpr size_t max_match_length = 258; // matches longer than these cannot be encoded using huffman codes - static constexpr u16 empty_slot = UINT16_MAX; - - struct CompressionConstants { - size_t good_match_length; // Once we find a match of at least this length (a good enough match) we reduce max_chain to lower processing time - size_t max_lazy_length; // If the match is at least this long we dont defer matching to the next byte (which takes time) as its good enough - size_t great_match_length; // Once we find a match of at least this length (a great match) we can just stop searching for longer ones - size_t max_chain; // We only check the actual length of the max_chain closest matches - }; - - // These constants were shamelessly "borrowed" from zlib - static constexpr CompressionConstants compression_constants[] = { - { 0, 0, 0, 0 }, - { 4, 4, 8, 4 }, - { 8, 16, 128, 128 }, - { 32, 258, 258, 4096 }, - { max_match_length, max_match_length, max_match_length, 1 << hash_bits } // disable all limits - }; - - enum class CompressionLevel : int { - STORE = 0, - FAST, - GOOD, - GREAT, - BEST // WARNING: this one can take an unreasonable amount of time! - }; - - static ErrorOr> construct(MaybeOwned, CompressionLevel = CompressionLevel::GOOD); - ~DeflateCompressor(); - - virtual ErrorOr read_some(Bytes) override; - virtual ErrorOr write_some(ReadonlyBytes) override; - virtual bool is_eof() const override; - virtual bool is_open() const override; - virtual void close() override; - ErrorOr final_flush(); - - static ErrorOr compress_all(ReadonlyBytes bytes, CompressionLevel = CompressionLevel::GOOD); + static ErrorOr> create(MaybeOwned, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); + static ErrorOr compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); private: - DeflateCompressor(NonnullOwnPtr, CompressionLevel = CompressionLevel::GOOD); - - Bytes pending_block() { return { m_rolling_window + block_size, block_size }; } - - // LZ77 Compression - static u16 hash_sequence(u8 const* bytes); - size_t compare_match_candidate(size_t start, size_t candidate, size_t prev_match_length, size_t max_match_length); - size_t find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t max_match_length, size_t& match_position); - void lz77_compress_block(); - - // Huffman Coding - struct code_length_symbol { - u8 symbol; - u8 count; // used for special symbols 16-18 - }; - static u8 distance_to_base(u16 distance); - size_t huffman_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths); - ErrorOr write_huffman(CanonicalCode const& literal_code, Optional const& distance_code); - static size_t encode_huffman_lengths(ReadonlyBytes lengths, Array& encoded_lengths); - size_t encode_block_lengths(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count); - ErrorOr write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional const& distance_code, size_t distance_code_count, Array const& code_lengths_bit_lengths, size_t code_length_count, Array const& encoded_lengths, size_t encoded_lengths_count); - - size_t uncompressed_block_length(); - size_t fixed_block_length(); - size_t dynamic_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array const& code_lengths_bit_lengths, Array const& code_lengths_frequencies, size_t code_lengths_count); - ErrorOr flush(); - - bool m_finished { false }; - CompressionLevel m_compression_level; - CompressionConstants m_compression_constants; - NonnullOwnPtr m_output_stream; - - u8 m_rolling_window[window_size]; - size_t m_pending_block_size { 0 }; - - struct [[gnu::packed]] { - u16 distance; // back reference length - union { - u16 literal; // literal byte or on of block symbol - u16 length; // back reference length (if distance != 0) - }; - } m_symbol_buffer[block_size + 1]; - size_t m_pending_symbol_size { 0 }; - Array m_symbol_frequencies; // there are 286 valid symbol values (symbols 286-287 never occur) - Array m_distance_frequencies; // there are 30 valid distance values (distances 30-31 never occur) - - // LZ77 Chained hash table - u16 m_hash_head[1 << hash_bits]; - u16 m_hash_prev[window_size]; + DeflateCompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibCompressor(move(buffer), move(stream), zstream) + { + } }; } diff --git a/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp b/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp index 14d655ed615..617f181d185 100644 --- a/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp +++ b/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index 01304a49e02..a18224c36c6 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -33,7 +33,7 @@ WebIDL::ExceptionOr> CompressionStream::construct_imp case Bindings::CompressionFormat::Deflate: return TRY(Compress::ZlibCompressor::create(move(input_stream))); case Bindings::CompressionFormat::DeflateRaw: - return TRY(Compress::DeflateCompressor::construct(make(move(input_stream)))); + return TRY(Compress::DeflateCompressor::create(move(input_stream))); case Bindings::CompressionFormat::Gzip: return TRY(Compress::GzipCompressor::create(move(input_stream))); } @@ -166,16 +166,9 @@ ErrorOr CompressionStream::compress(ReadonlyBytes bytes, Finish fini })); if (finish == Finish::Yes) { - TRY(m_compressor.visit( - [&](NonnullOwnPtr const& compressor) { - return compressor->finish(); - }, - [&](NonnullOwnPtr const& compressor) { - return compressor->final_flush(); - }, - [&](NonnullOwnPtr const& compressor) { - return compressor->finish(); - })); + TRY(m_compressor.visit([](auto const& compressor) { + return compressor->finish(); + })); } auto buffer = TRY(ByteBuffer::create_uninitialized(m_output_stream->used_buffer_size())); diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index 392a1f47920..8739417afac 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -34,7 +34,7 @@ WebIDL::ExceptionOr> DecompressionStream::construct case Bindings::CompressionFormat::Deflate: return TRY(Compress::ZlibDecompressor::create(move(input_stream))); case Bindings::CompressionFormat::DeflateRaw: - return TRY(Compress::DeflateDecompressor::construct(make(move(input_stream)))); + return TRY(Compress::DeflateDecompressor::create(move(input_stream))); case Bindings::CompressionFormat::Gzip: return TRY(Compress::GzipDecompressor::create((move(input_stream)))); } diff --git a/Tests/LibCompress/TestDeflate.cpp b/Tests/LibCompress/TestDeflate.cpp index c263009b240..9fd6ce77e2a 100644 --- a/Tests/LibCompress/TestDeflate.cpp +++ b/Tests/LibCompress/TestDeflate.cpp @@ -4,17 +4,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include - #include #include #include #include #include -#include -#include - -#define TEST_INPUT(x) ("deflate-test-files/" x) +#include TEST_CASE(canonical_code_simple) { @@ -30,7 +25,7 @@ TEST_CASE(canonical_code_simple) 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15 }; - auto const huffman = Compress::CanonicalCode::from_bytes(code).value(); + auto const huffman = TRY_OR_FAIL(Compress::CanonicalCode::from_bytes(code)); auto memory_stream = MUST(try_make(input)); LittleEndianInputBitStream bit_stream { move(memory_stream) }; @@ -50,7 +45,7 @@ TEST_CASE(canonical_code_complex) 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; - auto const huffman = Compress::CanonicalCode::from_bytes(code).value(); + auto const huffman = TRY_OR_FAIL(Compress::CanonicalCode::from_bytes(code)); auto memory_stream = MUST(try_make(input)); LittleEndianInputBitStream bit_stream { move(memory_stream) }; @@ -75,8 +70,8 @@ TEST_CASE(deflate_decompress_compressed_block) u8 const uncompressed[] = "This is a simple text file :)"; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == ReadonlyBytes({ uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == ReadonlyBytes({ uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(deflate_decompress_uncompressed_block) @@ -88,13 +83,13 @@ TEST_CASE(deflate_decompress_uncompressed_block) u8 const uncompressed[] = "Hello, World!"; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(deflate_decompress_multiple_blocks) { - Array const compressed { + Array const compressed { 0x00, 0x1f, 0x00, 0xe0, 0xff, 0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, @@ -105,8 +100,8 @@ TEST_CASE(deflate_decompress_multiple_blocks) u8 const uncompressed[] = "The first block is uncompressed and the second block is compressed."; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(deflate_decompress_zeroes) @@ -118,15 +113,15 @@ TEST_CASE(deflate_decompress_zeroes) Array const uncompressed { 0 }; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(uncompressed == decompressed.bytes()); } TEST_CASE(deflate_round_trip_store) { auto original = ByteBuffer::create_uninitialized(1024).release_value(); fill_with_random(original); - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original, Compress::DeflateCompressor::CompressionLevel::STORE)); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original)); auto uncompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); EXPECT(uncompressed == original); } @@ -136,19 +131,21 @@ TEST_CASE(deflate_round_trip_compress) auto original = ByteBuffer::create_zeroed(2048).release_value(); fill_with_random(original.bytes().trim(1024)); // we pre-filled the second half with 0s to make sure we test back references as well // Since the different levels just change how much time is spent looking for better matches, just use fast here to reduce test time - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original, Compress::DeflateCompressor::CompressionLevel::FAST)); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original)); auto uncompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); EXPECT(uncompressed == original); } TEST_CASE(deflate_round_trip_compress_large) { - auto size = Compress::DeflateCompressor::block_size * 2; - auto original = ByteBuffer::create_uninitialized(size).release_value(); // Compress a buffer larger than the maximum block size to test the sliding window mechanism + // Compress a buffer larger than the maximum block size to test the sliding window mechanism + auto original = TRY_OR_FAIL(ByteBuffer::create_uninitialized((32 * KiB - 1) * 2)); fill_with_random(original); + // Since the different levels just change how much time is spent looking for better matches, just use fast here to reduce test time - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original, Compress::DeflateCompressor::CompressionLevel::FAST)); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original)); auto uncompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(uncompressed.span() == original.span().slice(0, uncompressed.size())); EXPECT(uncompressed == original); } @@ -156,23 +153,5 @@ TEST_CASE(deflate_compress_literals) { // This byte array is known to not produce any back references with our lz77 implementation even at the highest compression settings Array test { 0, 0, 0, 0, 0x72, 0, 0, 0xee, 0, 0, 0, 0x26, 0, 0, 0, 0x28, 0, 0, 0x72 }; - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(test, Compress::DeflateCompressor::CompressionLevel::GOOD)); -} - -TEST_CASE(ossfuzz_63183) -{ - auto path = TEST_INPUT("clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz"sv); - auto test_file = MUST(Core::File::open(path, Core::File::OpenMode::Read)); - auto test_data = MUST(test_file->read_until_eof()); - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(test_data, Compress::DeflateCompressor::CompressionLevel::GOOD)); - auto decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); - EXPECT(test_data == decompressed); -} - -TEST_CASE(ossfuzz_58046) -{ - auto path = TEST_INPUT("clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz"sv); - auto test_file = TRY_OR_FAIL(Core::File::open(path, Core::File::OpenMode::Read)); - auto test_data = TRY_OR_FAIL(test_file->read_until_eof()); - EXPECT(Compress::DeflateDecompressor::decompress_all(test_data).is_error()); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(test)); } diff --git a/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz b/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz deleted file mode 100644 index 91158016c0355599f2659c385c088d5999742686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29687 zcmZo;mtnU*1H) z?&l1-rZE5q*Z47-0a?EcNL`!B=p{|tK=8TK$U>|tft z!_KgWlVJ}x!yaCSJ^T!N^aUCA2s7*vW!NLmut$<%k2J#`S%y9G40{wA_9!##QDxYp z&ag+5VUIS$9$kh#`V4yv8TJ@6>@j87W5KY;l3|ZE!ya3PJ@yQH92xdFGwg9?*yGNy z$CF`?H^UxZhCTiadjc8u1T*XjW!MwWuqTpXPc*}xScX0E40{q8_9QdxNoClR&afww zVNW*0o?M1K`3!ps8TJ%2>?vi~Q_irbl3`CZ!=74(J@pKG8X5L9Gwf+)*wfCir;}k% zH^ZJ@hCTfZdnPjMnar?fD#M=X40~oW?3vB5XD-8@`3!p&GVEE*uxBa5p5+XCRx<2a z&9G-J!=CjFdp0uc+03wKE5n}c410Dm?Agt*mtoI-hCK%v_8ex|bChAvafUr78TOoJ z*mIU)&v}MD7a8_kX4rF;Vb67jJvSNl+-BHwmtoI+hCL4%_B>|T^ORxFbA~-H8TPzp z*z=ZQ&wGYF9~t(1X4vzUVb6DlJwF-t{ASqmmtoI;hP{jodzl&bvNG&tXV}Zhu$P-* zFE7Jheulk*410wc_KGs>6=&Eh$*@{VyjtI4oen_;gm z!(M%cy@m{XjT!cuGVC>H*lWqK*P3CkEyG@WhP{pqdz~5fx-#r_XV~k>u-BVmuP?)1 ze}=t*410qa_J%U-4QJRJ$*?z?VQ(zM-gt(+i41#_8TO_!>`iCbo5`>@n_+J*!`^&` zy@d>Wiy8KoGVCp9*jvf4x0+#ZEyLb=hP{mpdz%^dwleH(XV}}xu(z9GZ!g2%euljh z8TL+Q*gKVB?{tQ}Ga2^IX4pHIVefo~y$c!kE@s%flwt33hP^8p_O52wyOv?^dWO9l z8TM{w*t?Zs?{>?{$W~HyQTcX4rd|Vefs0y$>1oK4#ealwt34hP^Kt_P%D=`<7wvdxpIq z8TNi=*!z`X?{|j1KNKr5GVEh#*vHARkDFm1FT*~5hJAtz z`-BZd4_$84EvNB_Ng-LQ)k$x$*@nGVV^F;K7EFLh79|R z8TOen>@#QBr>kpjXlh|;W@utzX<}$;Vq#`tVQgVwU}$P?WMXV-X~?k8nqi+U!#;b4 zeU1$KoEi4HGVF6_*yqWx&zoVNFT+0n+4_=I7t@Vx-tfHX+sFD(IgNL6%5!VZeb>_$ zJxVzzr6RH9K$?8k4=sdw-|9+KtLFQdLmqK(L7PcPf`4f1gUbIBl z@BY>Kv#zI?Z7*K5`W?^zx6!pmtWT~S?Qr|AFZ0KeyMBsAYJjlyikY)ZZZtid*Tons zTOs|RtZT{3_opP8-!Epm%258mOyIxj_8o^7iK_1ub3Ics-H$)*;_GugB|5TYqQBBV z*i4%%wp;VnNxge&vwq!Q;TrH_o$dB5_4|+9KNoXp_786DP{H*Ijte=TtTb>mV<~N6 zy?wUVeI9>s)w1$SY8wq&bdvj%wx3*Z=2?)HLdOO7f3?cnl;Sn|SX-D%+y3g=Z{1cL z8@+Ts54(o$mzM`CKeR|)o;uxsiI{+M7@KsN*>@h3S*FjvxR*v8uXkLcW-v45Xle8Z z_Bj$!mo}chJE?bS;LQB^zfbG59(6dy+inX?(>}1pa`L_W1|9c?Jne=;AL&KkE-Ra! z=CZxkR%mS9_wDkJG}XNe6a5z^Dl_DGolR$X<6t@M@L zVTs-REtU&5)Fhf6cb~}-l74FIV3KYn9`9Fq%{RYj0?`iiWV1)zZs9n2&v% z0@$0r>9$ay1thGI$1l;?kf=6~6}ZK>^Mal22G@;?1J zUnZw|bYtV;c72nJO}$Qe^JAl%ofYRjT{JIx!ImS=0%zvf_BUpI>v_|BQmf0bKW%mf zm$_0wl1*`zSpVk~MGvw4?%(V3`!*iid7bCbF=pSh`yaJTGZ5Z$@}B4s-lS|*S?PX# zr7!3IIeyvx@9D*V-aB5-{P}3}S(!g8lSTfnyrqBa*n{{GX|@m{!(R_R_CA(l%!*pR zdqc&geUV>_-YlN|G`I6#l)~wQ4O@F9pGeik@02;G@jg@~d8=9XE0(~G^PYSZHJ&E7 zp1Jziv&P$37(Hg4&#MTec@Z$)0FZ~-?hqq z^x4ggU%5KF(=RD7Y0jKemflRuzyJQb!=3x4XAs|GCdbVm)TU3_7Nqb+OT_iR!Tde% zPwk4~l26!|vXAe`pSeAO{g%oHucX&8v?xw>*!9?GTfy^~UDu|)lgZiHI!(qU(VX{6 zq}8<9k%vE?Q~VNt|I7}iMbqjV7A|%FdOK=%-n2WVm)~fu$bWTh%Z|r7o9wz9Dl#VU zUj5sub2sJ{<7%D_6R%B`E&p0>=6z+WUi8dWr(1Wvd+hAEZ_AWN?rJ){sv^NT3e((m zHQR&YJ{`K9FZU}`+NZn3q1@#{{`5EbE9U83P$QXlkKITn22*S;*<@Ne2_-Z{Qy^~OD)G`d>KS6#kx;kbI*j;K3} zr!f4UDR4VtPZ-yp>c?R#odV6LPw*99XdY{}HXuXy=u1(d=u_NBw=E4j^Cww_wMmcj zda>1%3+py*+-`ngu)oqD7Yk!Ku~{P3sc_g~6gzF(cBY_nBf27bCYWpb4L z{SyW=D>D*>m6Se-uTtHbJ1LW+U5i)l91Gv)6Y|%&=S#i0P$g}3KxkHJ{Hms>UiG{( z$&bJFFMibU91h%Da*~ z>BrS2#oQh?Pi)jo>^iyl9o>YwXW5@A`*P?)j_l6EcX*wn&LstW?0xFoFmbEmkGj7% zUA>6s}B=d0Ec#T69%?d9K2_2k(r;jyLHqv2tAeDdxrc119AK?0+?OCu}r-eERUC zuaRuisyo*yFBhDBOnKhrWxaoAGUhqmID0ej>*M(NwHi7PW7f^QX2t$QBVT*F+=*$5 zPixJSWCePc&5Zt~x##xG1t;5A*e;Zeo$~eP3x$3YyEN@T)jU3LUbzd`|97_Pdb;Gq zi!F~k%@Z64W>`9G0$PJfjlB6?dn)X3pfJ0=};n zT?%(My7a|6WxDp1D|ZaCer^rBKCxHR;%vgcdyQA;<#b)L5O|sVEIP1pBgd~VZcMv{ zRa*Axw3)5wKeW2WG5+{ciQ=^ZwP!8_-Qu16|MC5FB@X_xE_>KM@*G+9TcEk2)5|w^mF-|G|l|ORfp5;Qf)fY4VGQ)?Y%VV{=>8G(dyfouLT`kBfK>H#f7=nb)VNJ zJFMF_r$nFK%+9u&^Tax(#_1>b{JU#4>7{4J>vgMaW!`A1tYX+FT{dm&aAFuwu#YqQb>m%IEth9r#TeR_h2! zy`{6;>xqqd{fyA&`+rS8y(-TOKF&4om$SPcOX81rTkg6qeHzTye9zrr&Xx6BOg?Og zeav|3Q}A}%iN&0%JU#ou7fzLUb@Hdqw)2OyA8oC%6=8Fzb>+?0V_dbl^jP`q*X(?i z&rig!dUM~oS~Mr=1U&P_BnGB-14dn!BG`&Y@XDbGJg`roi1v9muJRg_#Om(Q3;l*>qyiP}Y^&g{Z|MPo4 z3nwLfH#xZcci&r^H6jkClivLLD3znmAra~G=)f^f_WYN*pSMj2{A5@x!dE1!DpPpu zpM&owvF61a4D{rU<4-gG=UX^wI;;PZuTPQ%7jCgNOnM{np=DqFkNf|bDsE{vcfV|w zW|{wW>D9ir7=h=jB^52zL&SN@r?n*R=#hW>*hp#K=EY07x-MP5x@?`T-PZ8K%N~hx zv21JcoA%}FDN{f8OTuTgBvNd=BIdsH6gNrAv|b^y*_X%Svr#O^ZrxI!jTcJh%S(0T zZS6C96Qxk}v4&GUmW{jh`>|C{Z-Zv|aL>vAp?3b{gM)nYbpA3WUf@r>@Z~UPu`0X! zZvSJyqeCWsaWPVDkhgs~%h@D^^|k+Dh0kv|tn&Yyoc&3LXS2trlK0y?bjoKbPn>7t z_>Skw;|~`k-)8yWR$lVfxLB28_F7q`?+ezJnkvOBy^~EeF0DJgTm&CHr2bS*|G8J6}QQYShsZ z?u_~Zj+oZJms%Sl4 z=M~gBE3;$nil&ap2E8LkKL=f2QRkQPV6WTZ=^V>NbQcDkaakgFjPNvMB=%7^PDds zXMQ!Sr5h!G)Bg9rAWSjzs)EJB*7c3LLeFmenUr)%-)_a7ZRaB`Kd%ziPReLIv1|3J zqahRG=fr$tf8so+ReSG*kO###4)<7e>%Ce1?8QQ%DD#<}QQd3Lt(+aqQfKWP!qqtc zL2K>KE21+FH=C?quA9ZMGfwvT7kTaQ2CmnOKW)-|@_h}r=&ZmM+s>|AG|ky*+O7+? zQg45*%lA3*v01xxvBQr^uCfOFv!74MnfYc{Vq3GW$u)`MywA)$8*03gR?baSSpU)N zl6~vxo^!nwuO2Y8}z9n$Jblw5Mr7jvv_s^=I;&peXmfyLl9Yx)+f>jzezI6dK-p?~v& zpl$vNR;`9NIYbS!{bEX%fAC%%!*Sd4)(*v8$E$yIv;4Z7))e{R>)s1t+jV^>FMDvO zy{TN|0ng7#3l-eF9;g}U7}RVJ-4(j^*r)nmdtJ7u^lXS(<(lLnr*r)3qh>w({?ReM{S~U)H^HM0NQYDf>yrD_R~+ z=re12xp-#84DIvTto3)Qci(HT2^9QL!~WFfi^Y^nH@5G+C*Sia{olH)cG{6Et|wnE z-53{E@O(jhgZZxT>8G>!&iwPW@RHMAxT#gK!()@^p4SuC*42ku&s_ajH%&sX`1y3l z$frMERBmWA6Bf;9KXm0UPhXJ5gm2S!pLuy>?fLfFA3{lM#LrBef9T$i#K!E9SyJpr zayr*nEqW4~m{?%fkR#W|{XAYq;9kqxe6~qi;oGzrRz2d|^hfiC`}Ot3uXc%h%okD! z>xtg8V8vS2#r(lvvVVoj%(q$X>2WIUGSlkiO*!IURFWFse0p(RclBNON6|ZrO$>P} zxK6iS>Un=D#9?1T)t!^SjSgANO0}JTY1W;Sb{*o}-fxdAe|D)tzGvMzp{Sm;-mv7& z()=%PcYS`!t%@N*llsIx5#V@*n73db;r(v z)5q%-Q&(=BEqbH#aNWP)jek}tKBzq8_f!4Xe+Xcjns?+C@1F;5^=nsK*c+@5O_;Vi zarLcP4b~N|#mx-+0vYxNGwcgx*cZ;QFOp$jG{e4FhJEo2`w|)UB{S?xW!RU_urHHg zUpB+OT!wx54EqWh_7yYiD`nVM&akhNVP7@FzFLNT^$hzO8TK_Z>}zG%*Uqr7lVM*s z!@gdIef1N!@lVZ`(`rio6WFqF2lb04Eq)`>|4yRZz;pR1O!@lhd`*t$y+s&|VFT=k54Eqi;>^sb`?vuwR;CzbwOkd4~Op4EvQC_Ny}NS7+F-$*^CWVZScJ zetm}hh79`+81|bo>^EoFZ^^LVnqj{!!+v{){f-R#of-DKGVFI}*zd`(-d{(6S}jSTyn8TPj_>~Ck--^sAQn_+)1!~TAT{Sz7X zPiELZm0|yMhW#@c_RnV6KbK+ue1`oC8TKz`*uRuv|8j=?D;f5$X4t=$VgGuD{Tmtf zZ)VuPm0|yOhW$Gk_U~rczn5YEK8Aw~`wuhhKgzKGIK%#v4Es+r>_5w}|2)I~iwyfO zGwi?0u>U&4{+kT@Z!_$_%dr1G!~Ta1`yVsxf6B1`Im7;!4EtX*?0?I!|2@P0j|}@i zGwlD$u>U*5{+|r{e>3d=%dr1H!vRKy1I!EuSQ!qmGaTS#IKa(tfS2I_Kf?h*h6BP3 z2SgbTh%+3JWH=zra6p#ffIPziMTP^)3{NM1}*&3&2S)>;XppafkK7@#S8~Z84i>) z9H?YCP|a|lmf=7>!+}PI1I-KvS{V+sGaTq-IMB^-pqJr5Kf{5E3*U^>Hr znG6SJGaQ)9a9}>efrShQ7Bd`J%5Y#g!-16y2Uas2Sj%u=J;Q;G3p5eeph6A4&4t!-e@SWkn zPlf}(84mnqIPjn0AS1&;W`={T36W;kfeaL}INpd-UUXNH5W3i^kz8d%W%-2;b0)c!C;1ip$rGZ84gA= z9E@f-7|U=lp5b63!@*>RgQ*M$(-{tCG91iiIGD?DFrVRIA;ZC9hJ&RH2g?}_Rx%u{ zW;j^OaIl`?U?ao9W`={U3^^vqp?C6B1_p+M2m2W&9z4h}o#Eh2hJ&*i z4$fsbIG^F*LWYBj84fOGIJlhQ;7W#rs~HZiWjMH=;owGwgPR!+Ze=*Qo#Eh4hJ(8q z4(??*xS!$RL572e84ez0ICz}l;7Nvqrx^~OWjJ`A;owDvgO?c&US&9Vo#Eh3hJ&{m z4&G%rc%R|mLxzKo84f;WIQX35;7f*suNe-$WjOer;owJxgP$1=eq}iLo#Eh5hJ(Ku z4*q30_@ChrBf}wPhC{3jhu9eoaWWj@W;n#laEPDbkRZb$VTMDZ42Q%S4oNZ`l4dw0 z%Wz1Z;gBN3A!UX`stkwJ84hVO9MWbuq|0zfpW%=p!y#jaL#7Oe%oz?@G90pIIAqIk z$e!VlBf}wQhC{9lhuj$sc`_XGW;o=_aLAwGP$0vhV1`4X42QxQ4n;B?ie@+z%Wx>3 z;ZP#Op=5?bsSJnG84hJK9Li=ml*@1^pW#p;!=Yk^L!}Id${7w-G90RAI8@7UsGi|a zBg3I)hC{6khuRqqbut|4W;oQ#aHyZ*&_sqqlNk<8WjHjQ;m}NmL$etU&1E<=pW)C# zhC_=P4lQLkw4CA4N`^zL84j&wIJBPO&_;$sn;8ymWjM5*;m}ToL%SIc?PWN$pW)C! zhC_!L4jpAUbe!SPNrpqG84jIgICP%j&_#wrml+OSWjJ)5;m}QnL$?_Y-DNm*pW)C$ zhC`1T4n1W!^qk?)ONK+Q84kT=IP{+3&_{+tpBWB)WjOSm;m}WpL%$gg{be}xpW!eg z!(nEI!>kO4*%=OVG92b+ILym%n4jUWAj4r{hQp!^hs7BVOEMgmW;iU%a9Ez$a6-5CyhG9311IPA-C*q`BWAj9EchQpx@hr<~TM=~6aW;h(na5$dfa3aIuWQN13 z42RPh4rekP&Sp5A%WycK;cy|t;bMlvr3{D584g!69Ij?KT+48{p5bsK!{KIz!>tU5 z+ZhgbG92z^INZx{xS!$hM25qY84gcnI6R%<@Jxoovl$N0WjH*a;qXF+!;2XXFJ(Bq zoZ;|FhQq5F4zFc6yq@9kMux+i84hn{IJ}MF@J@!qyBQAeWjMT_;qXC*!-p9TA7wav zoZ;|EhQp^B4xeQ>e4gR(MTWzd84h1%IDDPq@J)upw;2xKWjK7F;qXI-!;cvbKV>-l zoZ;|GhQqHJ4!>nM{GQ?PM~1_n84iDCIQ*UA@K1)rzZnkyWjOqw;Rqwc5oU%XtPDrk z8IEu=9N}g-!pm@kpW%og!x3SYBccpP#2JoAG8~a+I3mk%M4sV@BEu17h9jyBN7NaP zXfhnpW;mkDa73Tsh!MjPV}>K93`fivj#x4rv1T}8%W%Y=;fN!{5od-Yt_(-q8IE`| z9Pws2;>&QvpW#R#!;xTyBcTjO!WoW4G8~C!I1Y5Jj0QT3`Z_A9J$JHRF>hWJi}2%hNH?1M^zb)sxutbWH_qLa8#G! zs6N9{1BRo<3`b2Fj+!$ZwPZMI&2ZF~;ix^sQAdWO&J0Ig8IHO$9Q9;4>dkP}m*J>C z!_h#7qrnVELm7^SGaQX%I2z4xG?w9LJj2mMhNH<0M^hP&rZXJPWH_45a5R_UXg(ME=&%?w9d8IHCy9PMN{+RbpZm*HqX!_kQh zM<+8Joyu@@I>XVK3`b`(9G%N>bUwq;g$zd*GaOyYaCAAt(UlBGS2G-4%W!l(!_kcl zM>jJZ-8wqeJUZ1pI@L_F)|!uW;n*maEzVd7$?IqZiZvL49EBxjtMdx z6J|Ij%5Y4a;g}@DF=>WlvJA)M8ICD398+dErpj4u?)xJ8IC0~97|?6mdbD}o#9v}!?A3JW4R2+@)?d5G8`*rI9AGV zteoLkCBw05hGVr1$Lbl5H8LD)W;j;y-?HMrW!e8@-3-Ti8IJWc9Gl2+Y%;^KsSL-a zGaQ@AaBMcivAGP#<}(~y$Z%{i!?C3d$Cfi3Tgh;2HN&yB49C_p9NWlnY%{~LtqjMu zGaTE=aBMfjvAqn(_A?wi$Z+g1!?B|b$Br``JIQeDG{dp849Cth9J|PH>@vf#s|?4k zGaS3gaO^h2vAYb%?lT;F$Z+g&8N;!s49A``9DB)d>@~x&w+zSLGaUQKaO^X~v9Aor zzB3&A$#Cp9!?C{%$Nn=MXJk0e%y68Q;W#_PaZZNg+ziKg8IJQa92aCbF3fOTl;OBI zm@Un4T$bUuJi~EChU3Z%$5k1Qt1}$eWH_$Pa9o$+xIV*i1BT$0su!pUQB2I>Ygq4990P9G}Z@ zd_KeRg$&0RGaO&aaC|w#@s$k6S2G-6%W!->!|{y_$2T(^-^y@&JHzpv499mf9N)`u zd_TkSgAB(HGaNt4aQryK@skY4Pcs}p%W(WW!|{s@$1gJ+zshj@I>Ygs499OX9KXwO z{653+hYZIbGaP@)aQr#L@s|w8Uo#wk%W(WX!|{&{$3HV1|H^RuJHzpx499;n9RJI3 z{6E79Murp23@2C_POvka;AA+#&2WO3;RHX!2|_OPN*}S&}2BF&2U1O;eei9&`G#SAA(8BUZloTy|tQO$6omf=J_!-+U& z7Bie!%5Y*i!--Go0AVaAH5hiGvI$ z4l|rM%5dU1!-CJG`m*J#8!^uE~lfeupLm5tnGn|ZMI2p}wGM3?FJj2OEhLgz*CsP?t zrZb$(WH_14a59(SWIn^mLWYyY3@1w&PL?yAtYkP@&2X}o;bc9-$wr2g%?u}78BVq{ zoa|&c+0AgWm*HeT!^w#ZCnqzUoXT)=I>X7C3@2wZoSe&Waz4Y!g$ySbGn`z?aB?}r z$(0NzS2LVk%W!f%!^w>dCpR;k+{$orJHyGH3@3LpoZQQBazDe#gA6ARGn_oiaPm0A z$&(BxPcxi6%W(2M!^w*bCoeOcyvlI$I>X7E3@2|hoV?3$@;<}KhYTklGn{SN|)i3KEo+PhEv82r%V}6nKPWSWH@EbaLSh9ls&^KM}||*45wTfPPsFj@?<#W z&2Y+>;gmnasX&HP!3?KD8BT>WoQh;P70qxemf=)9!>L4uQ^^dcQW;LAGn~p~IF-$C zDwp9@KEtU(hEv51r%D-4l{1{GWH?pLaH^K!R6WC~Mut<(45wNdPPH?f>SQ?8&2Xxh z;Z#4vsfi4yCNrFx%5Z8r!>O4Jr)D#pn#*u%KEtVn45t<|oLb6oYB|HHl? zaB4lnsf`S$HZz>s%5Z8s!>OGNr*<=(+RJciKf|em45tn=oI1*I>NvxxlMJU$Gn_ih zaOyn6sf!G!E;F3E%5dsB!>OALr*1Qxy326tKEtVp45uD5oO;S|>N&%ymkg&~Gn{(M zaOyq7sgLis?_xOhnc>t|hEv}ePW@y!^_$_;Uxriv8BQ}YoMvV?&C0+I9WOY|&TyKO z;WRhHXbT`B4UWU{C z45ueDoSw{ZdMd-|=?tf5GMt{xaC$Do>G=$&7c!h)%y4=s!|CM=r<ZUd?cNEyL;c z45v3ToZifEdMm@}?F^@PGMwJcaC$Gp>HQ3+4>FuS%y9ZB!|CG;r%y7RKFx6YEW_#Z z45u$LoW9I(`YOZe>kOxFGMv86aQZI8>H7?)A2OVN%y9ZC!|CS?r(ZIhe$8HiF87#YqmGn`>%IK$3xhLhn8_un(T3}^Tm z&ImG`5oS0e%5X-U;fy518EJ+yvJ7YB8O|s&oKa>tqsnkbo#70_8Eu9$x(sLZ8O|6o zoH1rNW5#gCfZ>cK!x?LaGqwz8>>18DGMsT{IOEE2#+~7eC&L+UhBLklXZ#t?1Tvfn zW;heda3-ALOeDjZXofSf3}@mQ&LlFNNoF{c%5Wx~;Y=pOnQVqLxeRCW8O{_koGE5F zQ_66roZ(C*!KV>7GMs5V- zhBMO{&dg*uGn?VeT!u6A8O|(ZIJ21H%uV< zhBMn4&g^73vzy_}UWPOK8O|JJICGfc%u$9j#~IF?WH@u0;mlcvGv^u3Tx2+Nnc>V; zhBMb0&fH`;bDQDJU4}FF8O}UpIP;j{%u|Ll&l%3VWH|Gh;mlixGw&JBd}KKDnc>V= zhBMz8&irIJ^PAz!UxqXP8O|~?oMmP>%gS(;o#8Ae!&z>Iv%Cyv`5Dd%GMp7=I4jC< zR-ECiB*R&0hO@E^XXP2rDl(i^W;m8KXQIqr4fTd>Nzs8KVLjqkMt8KW{8qp}&Jav7uY8KVjrqly`$N*SZd8KWv0qpBIB zY8j*I8KW8*qna6`S{b9-8KXKGqq-TRdKsho8KWjLMongnn#ve8oiS=AW7KTMsJV<$ z^BJQSGDa`3vy4&a8KW*TMqOr%y2==JoiXYrW7KWNsJo0&_Zg!e zGDbaSjC#r#^_(&4C1ccU#;CW9QSTX}J~BpqW{mpE81L+8=Z^o#lvdrGDdG^jNZx^y`3?7Cu8((#^}9_(fb*r4>Cp{W{f_{7=4^E`XpoYX~yWYjM3*A zqc1W>UuKNH${2l}G5RKB^lirIyNuEI8KWOEMn7hZe##jAoH6<(WAtmr=(mi~?-`>% zGDd%9jQ+|P{hcxTCu8()#^}F{(f=7^7#U-j8Dm%(W7rvEI2mKO8Dn@EWB3_k1Q}z5 z8Dm5lW5gL_BpG9*8DnG_W8@iQ6d7Zb8Dmr#W7HXAG#O*G8Dn%AWAqtg3>agK8DmTt zW6T+2EE!|08Dnf2W9%7Y92sMr8Dm@-W84{IJQ-uW8Do4IWBeIo0vTh18Dl~jW5O9@ zA{k?%8DnA@W8xWO5*cHX8DmlzW6~L8G8tpC8Dnx8WAYhe3K?UH8DmNrW6Bw0Dj8#{ z8DnZ0W9k`W8X04n8Dm-*W7-*GIvHcS8Dn}GWBM6mCNjoMW{jE27&DzQW+r3IY{rnGR7Qcj5*2}bDS~eBxB5J#+b8=G3Oa$E;7bkW{kPY7;~L5<|bpzZN`|pj4}5a zV;(ZbJZ6k}${6#UG3F&>%xlJ&w~R6G89&A_#(ZXs`N|mcoiXMoW6W>Hn7@oM{~2Q$ z8Dp6lV_6wv*%@Ow8DqH_V|f{4`59vc8DoVRV?`Nb#TjEI8DpgxV`Uj*V|5v0^%-Lg8DotZV@(-j%^71Y8Dp&(V{I8@?HOYo8DpIpV_g|z z-5Fy&8DqT}V|^K8{TX8e8DoPPV?!BZ!x>{E8DpavV`CX(;~8TU8Do>ltGk8DpCnV_O+x+Zkg!8DqN{ zV|y86`x#>=GR973jGf9DJDo9hCS&Yu#@M-xvGW;Y7c#~!W{h3R7`vP?b|qu%YR1^L zjIrw(V>dF!Zf1<#${4$yF?J_o>~6-`y^OK@8DkGJ#vW#jJ<1q+oH6z!W9(_h*t3kW z=NV%!GR9tJjJ?Vjdz~@%CS&Yv#@M@zvG*BcA2P;1W{iEx82g+t_9bKNYsT2OjIr+- zV?Q#+erAmQ${72dG4>~8>~F@{zl^c}8RHlkc468RH}wP~X8RG&O${2T?G43Q|+-b(Rvy5@)8RIT8#$9HNyUG}MoiXkvW87`VxVwyT z_Zj0JGR8e-jC;x$_na~AC1c!c#<;hPaqk)9J~GCAW{msF826nq?k8j1Z^pR4jB)=N z;~5#_nHl3*8ROX*<2f1Qxf$bm8RPjG;{_Sxg&E^T8RNwn<0Tp6r5WR88RO*{;}sd> zl^Nqz8ROL%<24!MwHf1e8RPXC;|&?(jTz%j8RN|v<1HEEtr_EO8RP94;~g2}of+d@ z8ROj<<2@PUy&2{n;GL<8ROd-<2xDS zyBXtq8RPpI<0mr4PiBmt${0VLF@7dv{A|Yfxs37i8RHi+#xG`!U&lx!WGRALajNi%_znw9DCu96>#`wLA@%tI$4>HCdW{f||7=N5G{v>1kX~y`o zjPd6g<1aGCUuKNI${2s0G5#iF{B6egyNvPo8RH)^#y@6^f65sDoH70-WBhBz__vJl z?-}DiGRA*qjQ`3Q|D7@ZCu96?#`wRC@&6eU7#S0o8539;6WAFOI2jYT854LJ6Zjbu z1Q`>A852Yq6T}%4BpDN=853j~6XY2a6d4ng852|)6Vw?KG#L}L8549J6Z9Dq3>g!Q z852wy6U-SCEEyB5853+76YLoi92paw853L?6WkdSJQ)+b854XN6Z{zy0vQv6852So z6T%r2A{i5+853d|6XF>Y5*ZVc852?&6Ve$IG8q%H8543D6Y?1o3KlqUp855fs6I&S*+Zhu( z856r16MGpG`xz4_GA2%DOq|M?IGr(ZCS&4k#>BadiSrpF8B3xWOJW&I;u%X48B3BG zOHvt2(iuxK8B4MmOL7@Y@)=7C8B2;8OG+6_${9;48B3}eOKKTQ>KRKK8B3ZOOIjIA z+8Ika8B4kuOL`eg`WZ_mGL}qcESbt!GM%wxCS%EL#*(>=CG#0e7BZGBW-M9CShAe4 zWF=$CYQ~baj3w(COExl=Y-TLk%2=|Uv1BJ>$!^Ayy^JOM8A}c_mK&8A~}COSu_K zc^OOj8A}BjONALrMHx%Q8A~M@OQji0Wf@E58A}xzOO+W*RT)dw8A~-8OSKtGbs0 zW9dxB(%Foqa~VtLGnOu7EM3f4x|FeWIb-Qc#?sY{rE3{W*E5!GWGvmxSh|(5bUS0| zPR7#RjHP=SOZPLD9%L*%%vgGqvGh1&=}E@Y(~PBO8B5PImfmD6z06p8m9g|Xn0=eE z^e$uRea6y3hb~kBp_C8B4!1mVRd}{mEGRo3ZpS zW9fg!GDgNSX2vpB#xi!sGET-aZpJcR#xj1!GC{^NVa761#xilnGD*fVX~r^H#xi-v zGDXHRWyUg9#xixrGEK%ZZN@TP#xi}zGDF5PW5zO5#xirpGE2rXYsNBL#xi@xGDpTT zXT~yD#xi%tGEc@bZ^klT#xj4#vOvbNV8*gg#nPV#cyk#ZD%ao$ym0Vv1~76*?z{dgN$W|8Ox3`mK|p-JIPpf znz8IGW7&DevWtvmml?~hGL~IuEW62AcAK&6E@Rn!#@8#2d&aVljAfr0%f2#}eP=BD$yoNAvFtBn*?-1zM#gex#&TB1a(2dYPR4R>#&TZ9 za(>2gLB?`n#&S`{a&g9TNyc(%#&TK4a(TvbMaFVv#&T80a&^XXO~!I<#&TW8a(%{f zL&kDr#&T1}a&yLVOU80*#&TQ6a(l*dN5*nz#&TE2a(BjZPsVa@#&TcAa(~A1K*sW5 z#_~|c@^HrTNXGJL#`0Lk@_5GbM8@)D#`09g@^r@XOvdtT#`0Xo@_fefLdNo9#`03e z@^Z%VO2+bP#`0Rm@_NSdM#l1H#`0Fi@^;4ZPR8@`a4$iy6z8GL|oAEMLi3zM8RoEo1q5#`2Af<(nDHw=$M*XDr{z zSiYOFd@p19e#Y{HjOB+J%a1aaA7?B-$yk1xvHUD!`FY0ji;U%$8OyISmS1NqzsXpB zo3Z>ZWBGl?@`sG&j~UCKGL}DQEPu&Z{+hA;Eo1q6#`2Gh<)0bLzcQA8XDt88SpJ)_ z{4Znqf5r+%#tLS}3RcDncE$=$#tLr63SPzve#Qzx#tLD^3Q@)iamETs#tLc13R%Vq zdBzGw#tLP|3RT7mb;b%!#tLo53SGtueZ~qy#tLJ`3RA`kbH)lw#tLi33R}hsd&UY! z#tLV~3RlJocg6}&#tLu73SY(wf5wVH#)@FZicrRiaK?&A#)@dhide>qc*crE#)@Rd zid4pmbjFHI#)@plid@Eue8!4G#)@Lbic-dka>j~E#)@jjidx2sdd7-I#)@XfidM#o zcE*ZM#)@vnieAQwe#VN4j1`j^E2c75OlPc^$yhO)v0^S`#eBw!g^U%887r1DRxD?% zSjkwinz3RnW5s&Lij9mFn;9#%GFEJ7tk}s|v751CFJr}i#)^ZC6^9usjxtspXRJ8M zSaF)M;w)pudB%#1j1`v|E3PtDTxYDf$yjllvEnXc#eK$#hl~}E87rPLRy=2{c*$7t znz7<7W5s*MijRyHpBXE@GFE(NtoX@T@td*YFJr}j#!5!UN@m7NR>n$p#!61cN^ZtV zUdBp(#!5lPN@2!IQN~Jf#!5-XN@>PQS;k6v#!5xTN@d1MRmMtn#!5}bN^QnUUB*g% z#!5rRN@K=KQ^rbj#!5@ZN^8bSTgFOz#!5%VN@vDOSH?zm( z%3#LIP{zt|#>z;>%4o*QSjNhD#>zy-%4EjMRL075#>z~_%528UT*k_L#>zs*%3{XK zQpU=1#>z^@%4){STE@zH#>z&<%4WvOR>sP9#>!5{%5KKWUdGCP#>$C|m6I7Or!rPf zXRMsbSUH=qaxP=#e8$R!jFpQSE0;1>E@!M<$ym9Xv2rbA<$A`-jf|C>87sFkR&Hmk z+{swEo3U~)W95Fv%7cuRhZ!r6GFBdEtUSqBd782EEMw()#>$I~m6sVSuQFC%XRN%* zSb3YV@-AcLea6a%jFpeME1xn}K4+|a$yoWCvGOfr<$K1;kBpU{87sdsR(@x!{K;7P zo3ZjQW95IwDn`aCX2vR3#wvEkDo(~KZpJEJ#wvcsDnZ67Va6&^#wu~fDoMsFX~rs9 z#wvNnDn-UBWyUI1#wvBjDow^JZN@5H#wvZrDnrI9W5y~|#wv5hDoe&HYsM;D#wvTp zDo4gDXT~a5#wvHlDo@5LZ^kNL#wvftszAo7V8*IY#;S0}sz}DFXvV5o#;SP6szk=B zWX7sg#;SD2s!YbJY{sfw#;SbAszS!9V#cac#;S70s!GPHYR0Ns#;SV8sz%1DX2z;k z#;SJ4s!qnLZpNx!#;ShCs)>wMlNqa~GFDAzteVMKHJh<&E@Rbv#;S#kRf`#`mNHf? zXRKPuShbq5YAs{cdd8}aj8&T%tF|&$ZD*|7$yl|Uv1%`4)qcjRgN#*&8LN&mRvl-o zI>}gdnz8CEW7T=as*8+Oml>Mdi{d&a7dj8&f*tG+T;eP^ut$yoK9vFa~l)qlonM#gGp#%fl^YIeqIPR43( z#%f;1YJSFQLB?uf#%fWYIDYFOU7zz#%f!}YJ0|NN5*Pr#%fo_YInwJPsVC*#%f>2YJbM+ zK*s7|#_CYU>Tt&DNXF`D#_Cwc>UhTLM8@i5#_CkY>U75HOvdVL#_C+g>U_rPLdNQ1 z#_CeW>T<^FO2+DH#_C$e>UzfNM#k!9#_Cqa>UPHJPR8nP#_C?i>VC%RiHy~g8LOu< zR!?WFp2=7}o3VN>WA%K->V=Heiy5nzGFC5VtX|1jy_&IlEo1e1#_ElX)tecsw=!05 zXRO}ISiPIEdM{)3e#Yv9jMax3tB*2PA7`vS$yj}wvHC1y^?AnXi;UHm8LO`{R$phV zzR6g9o3Z*XWA%N;>W7Tgj~T0#u{P98d1g?amE@+#u{nH z8d=5~dBz$=#u{bD8db&`b;cS^#u{zL8ePU3ea0F?#u{VB8dJs^bH*A=#u{tJ8e7I1 zd&U|^#u{hF8dt^|cg7k|#u{(N8ehg5f5w_X#+qQpno!1?aK@TQ#+qoxnpnn~c*dGU z#+qctnpDP`bjF%Y#+q!#nq0=3e8!qW#+qWrno`D^a>klU#+quznp(!1dd8YY#+qiv znpVb|cE*}c#+q)%nqJ15e#V-Kj5U)PYo;>ROlPc_$yhU+v1Tq~&3wk1g^V?e8Ecj@ z)+}eNS;<(lnz3drW6gTTnvIM#n;C1iGS+Notl7y}vzxJIFJsMq#+rkSHHR5%jxyF9 zXRJBNSaX`O<}72)dB&QHj5U`TYpycZTxYDg$yjrnvF0vg&3(q2hm19k8Ec+0);wpd zdC6Gwnz80BW6gWUnvaY%pBZbuGS+-&tog}U^P92eFJsMr##%oR(##&Cs zT5iT#UdCE}##%wfT4BaoQN~(v##%|nT4}~wS;ks<##%+jT4lysRmNI%##&9rT5ZN! zUB+5{##%$hT4TmqQ^s0z##&3pT5HByTgF;@##%?lT4%;uSH@a*##&FtT5rZ$U&dO0 z#@ax}+F-`oP{!JD#@a~6+GxhwSjO6T#@a;2+GNJsRL0tL#@bBA+HA(!T*lgb#@a&0 z+G57qQpVbH#@b58+G@tyTE^OX#@a^4+GfVuR>se8$>^jJ1myYnL+CE@!M=$ymFZv34zE?Rv)Ajf}OM8EdyP z)^2C4-N{(Ho3VB;W9@#%+JlU>hZ$>+GS(hvtUbwCdz!KKEMx6?#@dUFwU-%duQJwN zXRN)+SbLkX_AX=Xea6~{jJ1y$Yo9XKK4+|b$yocEvGy%v?R&=BkBqgS8Ed~X)_!NK z{mEGSo3ZvUW9@&&I!4AiX2v>J#yWP!I!?wqZpJ!Z#yWn+Izh%dVa7U9#yWAvI!VSl zX~sHP#yWY%Iz`4hWyU&H#yWMzI!(qpZN@rX#yWk*Izz@fW5zmD#yWGxIt#`+YsNZT z#yWe(I!DGjXT~~L#yWS#I#0$rZ^k-b#yWq-x$Wo1ZD*|8$ym3Wv2HJ8-G0WpgN${D8S9QR z)*WZ8JIPpgnz8OIW8Hbix{Hi;ml^A>GS*#Zth>orcbl>9E@Rz&#=3`$b&nbAo-)=w zXRLe4SofN-?k!{8d&attjCG$G>%KD9eP^uu$yoQBvFu`g+FtM#lPP#`;#q`gX?pPR9Cf#`<2y`hLdxiH!A= z8SAGq)=y`wpUGH1o3VZ_WBq)_`h|@3iy7;eGS)9=tY67kznZarEo1$9#`=wn^_v;% zw=&jmXRP1JSihUGelKJFe#ZKPjP-{Z>yI+lA7`vT$yk4yvHmP${dvavi;VS`8SAey z)?a6=zsXpCo3Z{bWBq-``iG45j~VNqGS)w5tbfT^|C+J>Eo1$A#`=$p^`9B*zcSW; zXRQCpSpS=`{x4(wf5rwz#s+4_23E!fcE$!y#s+T2242Pne#Qnt#s*==22sWaamEHo z#s+D|23f`idBz4s#s+1^235ueb;brw#s+Q123^JmeZ~eu#s*`?22;icbH)Zs#s+J~ z23y7kd&UMw#s+7`23N)gcg6-!#s+W324BVof5wJD#)e?VhET?aaK?s6#)fFdhFHdi zc*cfA#)f3ZhE&FebjF5E#)fRhhFr#me8z@C#)e|XhEm3ca>j;A#)fLfhFZpkdd7xE z#)f9bhE~RgcE*NI#)fXjhF->oe#VB0j17|+8>TWgOlNGE$=EQPv0*M_!+geug^Uf0 z85@=|HY{gsSjpJ1nz3OmW5asJhK-C3n;9FnGB#{yY}m=zu$!@AFJr@g#)gB84Tl*U zjxshJXKXmh*l?P$;VfgrdB%o|j189=8?G`oTxV>!$=Gn4vEeRb!+pkvhl~x685^E5 zHaur+c*)rCnz7+6W5avKhL4O5pBWpzGB$i?Z1~C8@SCyWFJr@h#zscQMrOuFR>nql z#zs!YMsCJNUdBd##zsNLMq$QAQN~7b#zslTMrp=IS;j_r#zsZPMrFoERmMhj#zsxX zMs3DMUB*Uz#zsTNMq|cCQ^rPf#zsrVMr+1KTgFCv#zsfRMrX!GSH?zn#zs%ZMsLPO zU&cm%#>PO##$d+AP{zh^#>Pm-#%RXISjNV9#>Pa(#$?9ERK~`1#>Py>#%#vMT*k(H z#>PU%#$v|CQpUz|#>Ps<#%jjKTE@nD#>Pg*#%9LGR>sD5#>P&@#%{*OUdG0L#>R<^ zjguK0r!qE9XKb9w*f^W9aV}%ye8$FwjE##K8<#RRE@y09$=JA>v2iV9<9f!%jf{<( z85_4UHg0EZ+{xIuo3U{(W8;3t#)FKFhZ!4>GBzG(Y&^->c$%^CEMwz&#>R_`jh7i4 zuQE1XXKcL5*m#?<@h)TIea6OzjE#>O8=o>ZK4)xv$=LXsvGFZq<9o)&kBp6<85_Sc zHhyPp{K?q(o3ZgPW8;6uCPv04X2vE~#wK>gCQimCZpJ2F#wLEoCPBs~Va6s=#wKyb zCP~I7X~rg5#wK~jCPl_3WyU5|#wK;fCQZgBZN?^D#wLBWCPT(1W5y;^#wK&dCQHU9 zYsMy9#wL5lCP&65XT~O1#wK^hCQrsDZ^kBH#wLHpra;D~V8*6U#-?z_rbx!7XvU^k z#-@12rbNc3WX7gc#-?<}rcB1BY{sTs#-@D6rb5Q1V#cOY#-?({rb@=9YR0Bo#-@74 zrbfo5X2zyg#-?`0rcTDDZpNlw#-@J8riqMAlNp<)GB!O`GB#ajY`V$Vbepm1E@RVu#-@jiO^+Fy zo-#H)XKZ@O*z}sQ=`CZ^d&Z`Zj7^^zo4ztOeP?X?$=LLpvFR^k(|^WhM#g4l#%5N= zW_HGAPR3?##%5l|W`4$ILB?ib#%58*W^u-5NycVr#%5W@W_iYDMaE`j#%5KW_QMBPsV0% z#%5o}W`D-!K*r`^#^zAQ=5WU5NXF)9#^zYY=6J^DM8@W1#^zMU=5)s9OvdJH#^zkc z=6uHHLdND|#^zGS=5og7O2+1D#^zea=6c5FM#ko5#^zSW=61&BPR8bL#^zqe=6=TJ ziHyyY8JnjvHcw}4p2^reo3VK=WAl8*=7o&Siy51jGBz(~Y+lLOyqd9jEo1X~#^#NT z&6^pUw=yY zzcMy|XKen-*!-KZ`7dMhf5sL@#ujGA7FNa21#uiV;7H`HDU&a=H#+E?FmSDz~P{x*U#+FFNmT1P7 zSjLuk#+F3JmSo13RK}Kc#+FRRmTbnBT*j7s#+E|HmSV=1QpT2Y#+FLPmTJb9TE>=o z#+F9L7RHw5R>qcg#+FXTmTtzDUdEPw#+HeUEt45rrZTonXUwu?$g*e1a%9MIX2^17 z$Z}`M@?^;JX2|ko$ns~%3S`I%X2=R<$O>o3ie$)&X2^dK$SP;Zs$|HjX2_~#$f{?^YGlZ2X2@z~$ZBWE>SW03 zX2|Mg$m(avn#hnfnIUT`L)LVLteFg1vl+7HGGxtX$XdvdwU{AmDMQwBhOCtgS*sbc z)-q(RXUN*fkhPg1Yb!(6c808-3|YGwvi34$?Pth3$dGlIA?qkZ)^UcclMGp>8M4kY zWSwWoy2y}qnIY>cL)LYMteXs3w;8hTGGyIn$a=_-^_U^+DMQwChOCziS+5zg-ZEsp zXUO`nlUncZRH=3|YS!vi>q;{b$H#WXNV_$Yy28W@pIeWXR@b$mV6p=4Z$j zWXKj~$QEVD7H7zoWXP6g$d+ZumS@OTWXM)#$W~>@R%gi8WXRTL$kt`Z)@R5zV#qdT z$TnrjHfPATWXQH=$hKw3wr9w8WXN`A$aZDOc4x@;WXSer$o6H(_GicrWXKL?$PQ)5 z4rj=YWXO(Y$c|;mj%UbDWXMit$WCR*PG`u@WXR5D$j)WR&S%IjWXLXN$S!5bE@#ND zWXP^&$gX9`u4l+@WXNu2$ZloGZfD5uWXSGj$nIsx?q|rJ$dEmmA$uxA_H>5qnGD&p z8M5axWY1^FUdWKWm?3*9L-ulp?3E1Js~NJ_GGwo3$ll11y_q3$YEy4VP(i+XUO4X$l+$l;bq9-XUGv`$Ps495oO2`XULIc$dP8qk!8q{XUI`x z$Wdm#@ny*I&tV8;$O&f131!F$XUK_U$cbjiiDk%%XUIup$Vq0%NoB}M zXUNH9$jN5N$z{mNXUHjJ$SG#XDP_nhXUM5!$f;(?sb$EiXUJ(}$Z2NCX=TW1XUOSf z$mwRt>1D|2XULhzkTaPfXDUO^bcURn3^}tIa^^DR%xB11$dI#`A!jK=&T@vFl?*wn z8FJP#&>Rc6RlWyn=$$kk-X)n>@mWysZM$Teih zWiU3)Wym#W$hBn1wPwh*WyrN>$aQ4Mb!NzQWyp1B$n|8%^=8QRWytks$PHx34Q9v< zWylR@$c<#kjb_M=Wyp6cPT^ea)#WM47sZra@R8Cu4l;I$dJ34A$Kc7?skUUoea6V z8FKeB;Esrx|k3GUT3T$i2vrdzm5kDnssdhTNMBxwjc| z?=s}xXUKiXko%Y+_bEf}bB5fP47slva^EuKzGuk&$dLP)A@?go?staVpA5Ob8FK$J z7<7CL=X2|1Z$m3_o6J*E}X2=s|$P;JClVr$~X2_Fe$dhNt zQ)I|fX2?@z$Wv#?(`3lgX2{cJ$kS)YD^6!D&SWgkW-QKSEY4>vE@Uh&W-KmcEG}m( zu4F8(W-P8{EUsrPZe%QOW-M-HEN*8k?qn?PW-RVyEbeD4p2%1{nXz~(WASvx;+c%a zvl)x$G8WHgEMCZ1yqK|gDP!?+#^RNX#j6>M*D@BbXDr^xSiG6Bcq?P^cE;kJjK#Yd zi}x}X?`JGN$XI-svG^!s@o~oDlZ?fu8H>*{7N2J_$y=acgEtMjK#khi~lkf z|7R>=WGrE3EMa9VVP`DiWGvxkEa7D=;b$xnWGoS8ED>ca5oausWGsS31=)BowpyIw;!FiA35{(e}9*OS4@n~ z+mFuMhck}O+vAzHPiCl1WvESOsLf=k&1R_0WvI<(s4ZluEoP`KWvDG@sI6qEt!AjL zWvH!ZsBL7ZZDy!#WvFdusO@B^?PjR$WvK0EsGZ1AJDH(&DnspbhT547wX+#&=Q7mJ zXQ*AsP`j9+b}2*ca)#QK47IBnYS%K``n?RJLRoeZ_R8EW@3)b3}f zJ;+ddn4$J4L+x>f+LH{mrx|L`GSr@DsJ+Ngdzqp3DnspchT5A9wYM2+?=sZhXQ+M1 zQ2Usn_9;W{bB5ZN47INrYTq){zGtZY$WZ&4q4q07?RSRSpA5CX8EXGB)c$9vV`Qjf zW~gIjsAFfS<7BAgW~k$3sN-j-6J)3pW~dWos1s+XlVqrqW~h^8sFP=?Q)H-9W~ftT zs8eUC(`2aAW~kF;sMBYtGi0bUW~eh|s54-wvt+2VW~j4esIzCNb7ZJqi z^JJ*=W~lRJsPkv23uLGZW~d8gs0(MPi)5&aW~hs0sEcQ)OJt}^W~fVLs7q(4%Vem_ zW~j?$sLN-lD`coEW~eJ=s4Hivt7NFFW~i%WsHtv|wW~l3B zsOx8_o5)Z%nW1hfL)~h?0!?PsVv$WV8f!R{zS-EoGxlMHpI8S2h5)SYLj zyU0*?nW63~L)~?Tx|i#m+{b#6WWTsMlqv*Jr3VWT-c0s5fP( zH)p7~WT>}hsJCUPw`Zt#WTgWT^LMsP|>4_h+aNWT+2js1IeR4`-;4 zWT=m3sE=i+k7uY)WT;POs8406PiLslWT?+(sLy4n&u6GFWT-D@s4r!xFK4K)WT>xZ zsIO(HuV<)lWTd!LNpJ%AQ$WVWoq5djE{dI==n+)~08S3vc)Zb^Q zf5=e(n4$hDL;Z7x`j-s#uNms!GSt6ksQ<`N|CyowD?|NvhWeij^}iYF|1#A7XJ}w# zXkcb&U}b1vXK3JLXy9gO;ALpwXJ`;)Xb@&-5M^i(XK0XQXpm-TkY#9)XJ}AlXi#Qo zP-SRPXK2u5XwYV8&}C@QXJ`Oj#$dtFV9d~9%FtlI&|t~XV9n5A%g|uY(BR0>;LOnA z%Fy7>(BR3?;LXtB%h2G@&=APb5X{gJ%Fqzb&=ASc5Y5mK%g_+d(2&T`kj&7K%FvL` z(2&W{kj>DL%g~U|&``+GP|VO!%Fs~G&``3=K0G8fG&z%w=ep&(N@tpK0$k4EvpDGaG9auDnr9{hK8FA4YwH@?lLsoXJ~lH(D0a{;VDDIbB2bO3=OXt8s0KAyk}_m z$k6baq2Vh-!*_;;p9~GZ85;gFH2i01WMpV$W@uz(Xk=$-;XcT8?lw@d>W@waUXq0DYRAgvWW@uDpXjErt)MRMXW@yx9Xw+wDG-PNrW@t2J zXf$VNv}9Xl!R_>||){W@zkXXzXWboXF5PnW1qiL*sOY#+eL_vl$xa zGBnO-Xk5t9xR{}FDMRCOhQ^f)jjI_N*D^G&XK38W(72hQaVtaPc812C42`=P8uv0Z z?q_H`$k2G0q46j~<8g+@lMIcg85++rG@fT@yvWdanW6D2L*sRZ#+wX{w;3AmGBn<2 zXne@f_?V&bDMRCPhQ^l+jjtIR-!e45XK4J$(D<35@hd~)cZSBF42{1T8vimh{%2@n zWN2b$Xkuk(VrOXLWN6}MXyRpP;%8_QWM~p*XcA>;5@%?VWN4CRXp&`Ul4odAWN1=m zXi{ZpQfFw=WN6Z6Xwqe9(r0KgVQ4aDXfkDJGGJ)3WN5NxXtHH!vS(;=WN30`XmVv} za%W&@%429MWN0d8XewoBDrab_WN4~pXsTsss%L0wWN2z;Xli9>YG-KbWN7MUXzFEX z>St)0$j~&Ip=l~Z({zTWnG8*{8JgxYG|gvdTFB6}n4xJYL(`${%Nd$hGBmAbXj;qA zw4R}9BSX_>hNi6yP1_lob}}^WW@y^W(6pbS=^#VXVTPuo3{A%wnocq_on~k{%g}V5 zq3I$+(`AOHs|-!o8Jcb~G~H%sy35dXpP}g?L(^l1rl$-|&l#FtGBmwrXnM=g^q!&V zBSX_?hNiC!4F4ILntnDhH2r4y{ Date: Sat, 1 Mar 2025 17:53:45 +0100 Subject: [PATCH 064/141] LibCrypto: Remove unused `Adler32` class --- Libraries/LibCrypto/CMakeLists.txt | 1 - Libraries/LibCrypto/Checksum/Adler32.cpp | 48 ------------------------ Libraries/LibCrypto/Checksum/Adler32.h | 38 ------------------- Tests/LibCrypto/TestChecksum.cpp | 15 -------- 4 files changed, 102 deletions(-) delete mode 100644 Libraries/LibCrypto/Checksum/Adler32.cpp delete mode 100644 Libraries/LibCrypto/Checksum/Adler32.h diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index 4277f33db6b..06248104ac3 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -17,7 +17,6 @@ set(SOURCES BigInt/SignedBigInteger.cpp BigInt/UnsignedBigInteger.cpp Certificate/Certificate.cpp - Checksum/Adler32.cpp Checksum/cksum.cpp Checksum/CRC32.cpp Cipher/AES.cpp diff --git a/Libraries/LibCrypto/Checksum/Adler32.cpp b/Libraries/LibCrypto/Checksum/Adler32.cpp deleted file mode 100644 index eaff83f7d3c..00000000000 --- a/Libraries/LibCrypto/Checksum/Adler32.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -namespace Crypto::Checksum { - -void Adler32::update(ReadonlyBytes data) -{ - // See https://github.com/SerenityOS/serenity/pull/24408#discussion_r1609051678 - constexpr size_t iterations_without_overflow = 380368439; - - u64 state_a = m_state_a; - u64 state_b = m_state_b; - while (data.size()) { - // You can verify that no overflow will happen here during at least - // `iterations_without_overflow` iterations using the following Python script: - // - // state_a = 65520 - // state_b = 65520 - // for i in range(380368439): - // state_a += 255 - // state_b += state_a - // print(state_b < 2 ** 64) - auto chunk = data.slice(0, min(data.size(), iterations_without_overflow)); - for (u8 byte : chunk) { - state_a += byte; - state_b += state_a; - } - state_a %= 65521; - state_b %= 65521; - data = data.slice(chunk.size()); - } - m_state_a = state_a; - m_state_b = state_b; -} - -u32 Adler32::digest() -{ - return (m_state_b << 16) | m_state_a; -} - -} diff --git a/Libraries/LibCrypto/Checksum/Adler32.h b/Libraries/LibCrypto/Checksum/Adler32.h deleted file mode 100644 index 1f0292c0208..00000000000 --- a/Libraries/LibCrypto/Checksum/Adler32.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2020-2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Crypto::Checksum { - -class Adler32 : public ChecksumFunction { -public: - Adler32() = default; - Adler32(ReadonlyBytes data) - { - update(data); - } - - Adler32(u32 initial_a, u32 initial_b, ReadonlyBytes data) - : m_state_a(initial_a) - , m_state_b(initial_b) - { - update(data); - } - - virtual void update(ReadonlyBytes data) override; - virtual u32 digest() override; - -private: - u32 m_state_a { 1 }; - u32 m_state_b { 0 }; -}; - -} diff --git a/Tests/LibCrypto/TestChecksum.cpp b/Tests/LibCrypto/TestChecksum.cpp index 93b370a1330..2c5574266b3 100644 --- a/Tests/LibCrypto/TestChecksum.cpp +++ b/Tests/LibCrypto/TestChecksum.cpp @@ -4,25 +4,10 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include -TEST_CASE(test_adler32) -{ - auto do_test = [](ReadonlyBytes input, u32 expected_result) { - auto digest = Crypto::Checksum::Adler32(input).digest(); - EXPECT_EQ(digest, expected_result); - }; - - do_test(""sv.bytes(), 0x1); - do_test("a"sv.bytes(), 0x00620062); - do_test("abc"sv.bytes(), 0x024d0127); - do_test("message digest"sv.bytes(), 0x29750586); - do_test("abcdefghijklmnopqrstuvwxyz"sv.bytes(), 0x90860b20); -} - TEST_CASE(test_cksum) { auto do_test = [](ReadonlyBytes input, u32 expected_result) { From 6b2515657cd1f8a56b614b99ba4147c7ca49b0c6 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:54:54 +0100 Subject: [PATCH 065/141] LibCrypto: Remove unused `CRC32`, `CRC16` and `CRC8` classes --- Libraries/LibCrypto/CMakeLists.txt | 1 - Libraries/LibCrypto/Checksum/CRC16.h | 82 ------------ Libraries/LibCrypto/Checksum/CRC32.cpp | 176 ------------------------- Libraries/LibCrypto/Checksum/CRC32.h | 36 ----- Libraries/LibCrypto/Checksum/CRC8.h | 76 ----------- Tests/LibCrypto/TestChecksum.cpp | 13 -- 6 files changed, 384 deletions(-) delete mode 100644 Libraries/LibCrypto/Checksum/CRC16.h delete mode 100644 Libraries/LibCrypto/Checksum/CRC32.cpp delete mode 100644 Libraries/LibCrypto/Checksum/CRC32.h delete mode 100644 Libraries/LibCrypto/Checksum/CRC8.h diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index 06248104ac3..5bb84381955 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -18,7 +18,6 @@ set(SOURCES BigInt/UnsignedBigInteger.cpp Certificate/Certificate.cpp Checksum/cksum.cpp - Checksum/CRC32.cpp Cipher/AES.cpp Curves/EdwardsCurve.cpp Curves/SECPxxxr1.cpp diff --git a/Libraries/LibCrypto/Checksum/CRC16.h b/Libraries/LibCrypto/Checksum/CRC16.h deleted file mode 100644 index 22112be7ba4..00000000000 --- a/Libraries/LibCrypto/Checksum/CRC16.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2023, kleines Filmröllchen . - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Crypto::Checksum { - -// A generic 16-bit Cyclic Redundancy Check. -// Just like CRC32, this class receives its polynomial little-endian. -// For example, the polynomial x¹⁶ + x¹² + x⁵ + 1 is represented as 0x8408. -template -class CRC16 : public ChecksumFunction { -public: - static constexpr u16 be_polynomial = bitswap(polynomial); - - // This is a big endian table, while CRC-32 uses a little endian table. - static constexpr auto generate_table() - { - Array data {}; - data[0] = 0; - u16 value = 0x8000; - auto i = 1u; - do { - if ((value & 0x8000) != 0) { - value = be_polynomial ^ (value << 1); - } else { - value = value << 1; - } - - for (auto j = 0u; j < i; ++j) { - data[i + j] = value ^ data[j]; - } - i <<= 1; - } while (i < 256); - - return data; - } - - static constexpr auto table = generate_table(); - - virtual ~CRC16() = default; - - CRC16() = default; - CRC16(ReadonlyBytes data) - { - update(data); - } - - CRC16(u16 initial_state, ReadonlyBytes data) - : m_state(initial_state) - { - update(data); - } - - // FIXME: This implementation is naive and slow. - // Figure out how to adopt the slicing-by-8 algorithm (see CRC32) for 16-bit polynomials. - virtual void update(ReadonlyBytes data) override - { - for (size_t i = 0; i < data.size(); i++) { - size_t table_index = ((m_state >> 8) ^ data.at(i)) & 0xFF; - m_state = (table[table_index] ^ (static_cast(m_state) << 8)) & 0xFFFF; - } - } - - virtual u16 digest() override - { - return m_state; - } - -private: - u16 m_state { 0 }; -}; - -} diff --git a/Libraries/LibCrypto/Checksum/CRC32.cpp b/Libraries/LibCrypto/Checksum/CRC32.cpp deleted file mode 100644 index 87699a3a81d..00000000000 --- a/Libraries/LibCrypto/Checksum/CRC32.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2020-2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include - -#ifdef __ARM_ACLE -# include -#endif - -namespace Crypto::Checksum { - -#if __ARM_ARCH >= 8 && defined(__ARM_FEATURE_CRC32) && defined(__ARM_ACLE) -void CRC32::update(ReadonlyBytes span) -{ - // FIXME: Does this require runtime checking on rpi? - // (Maybe the instruction is present on the rpi4 but not on the rpi3?) - - u8 const* data = span.data(); - size_t size = span.size(); - - while (size > 0 && (reinterpret_cast(data) & 7) != 0) { - m_state = __crc32b(m_state, *data); - ++data; - --size; - } - - auto* data64 = reinterpret_cast(data); - while (size >= 8) { - m_state = __crc32d(m_state, *data64); - ++data64; - size -= 8; - } - - data = reinterpret_cast(data64); - while (size > 0) { - m_state = __crc32b(m_state, *data); - ++data; - --size; - } -} - -// FIXME: On Intel, use _mm_crc32_u8 / _mm_crc32_u64 if available (SSE 4.2). - -#else - -static constexpr size_t ethernet_polynomial = 0xEDB88320; - -# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - -// This implements Intel's slicing-by-8 algorithm. Their original paper is no longer on their website, -// but their source code is still available for reference: -// https://sourceforge.net/projects/slicing-by-8/ -static constexpr auto generate_table() -{ - Array, 8> data {}; - - for (u32 i = 0; i < 256; ++i) { - auto value = i; - - for (size_t j = 0; j < 8; ++j) - value = (value >> 1) ^ ((value & 1) * ethernet_polynomial); - - data[0][i] = value; - } - - for (u32 i = 0; i < 256; ++i) { - for (size_t j = 1; j < 8; ++j) - data[j][i] = (data[j - 1][i] >> 8) ^ data[0][data[j - 1][i] & 0xff]; - } - - return data; -} - -static constexpr auto table = generate_table(); - -struct AlignmentData { - ReadonlyBytes misaligned; - ReadonlyBytes aligned; -}; - -static AlignmentData split_bytes_for_alignment(ReadonlyBytes data, size_t alignment) -{ - auto address = reinterpret_cast(data.data()); - auto offset = alignment - address % alignment; - - if (offset == alignment) - return { {}, data }; - - if (data.size() < alignment) - return { data, {} }; - - return { data.trim(offset), data.slice(offset) }; -} - -static constexpr u32 single_byte_crc(u32 crc, u8 byte) -{ - return (crc >> 8) ^ table[0][(crc & 0xff) ^ byte]; -} - -void CRC32::update(ReadonlyBytes data) -{ - // The provided data may not be aligned to a 4-byte boundary, required to reinterpret its address - // into a u32 in the loop below. So we split the bytes into two segments: the misaligned bytes - // (which undergo the standard 1-byte-at-a-time algorithm) and remaining aligned bytes. - auto [misaligned_data, aligned_data] = split_bytes_for_alignment(data, alignof(u32)); - - for (auto byte : misaligned_data) - m_state = single_byte_crc(m_state, byte); - - while (aligned_data.size() >= 8) { - auto const* segment = reinterpret_cast(aligned_data.data()); - auto low = *segment ^ m_state; - auto high = *(++segment); - - m_state = table[0][(high >> 24) & 0xff] - ^ table[1][(high >> 16) & 0xff] - ^ table[2][(high >> 8) & 0xff] - ^ table[3][high & 0xff] - ^ table[4][(low >> 24) & 0xff] - ^ table[5][(low >> 16) & 0xff] - ^ table[6][(low >> 8) & 0xff] - ^ table[7][low & 0xff]; - - aligned_data = aligned_data.slice(8); - } - - for (auto byte : aligned_data) - m_state = single_byte_crc(m_state, byte); -} - -# else - -// FIXME: Implement the slicing-by-8 algorithm for big endian CPUs. -static constexpr auto generate_table() -{ - Array data {}; - for (auto i = 0u; i < data.size(); i++) { - u32 value = i; - - for (auto j = 0; j < 8; j++) { - if (value & 1) { - value = ethernet_polynomial ^ (value >> 1); - } else { - value = value >> 1; - } - } - - data[i] = value; - } - return data; -} - -static constexpr auto table = generate_table(); - -void CRC32::update(ReadonlyBytes data) -{ - for (size_t i = 0; i < data.size(); i++) { - m_state = table[(m_state ^ data.at(i)) & 0xFF] ^ (m_state >> 8); - } -} - -# endif -#endif - -u32 CRC32::digest() -{ - return ~m_state; -} - -} diff --git a/Libraries/LibCrypto/Checksum/CRC32.h b/Libraries/LibCrypto/Checksum/CRC32.h deleted file mode 100644 index 97322f58e7b..00000000000 --- a/Libraries/LibCrypto/Checksum/CRC32.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2020-2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Crypto::Checksum { - -class CRC32 : public ChecksumFunction { -public: - CRC32() = default; - CRC32(ReadonlyBytes data) - { - update(data); - } - - CRC32(u32 initial_state, ReadonlyBytes data) - : m_state(initial_state) - { - update(data); - } - - virtual void update(ReadonlyBytes data) override; - virtual u32 digest() override; - -private: - u32 m_state { ~0u }; -}; - -} diff --git a/Libraries/LibCrypto/Checksum/CRC8.h b/Libraries/LibCrypto/Checksum/CRC8.h deleted file mode 100644 index 8850162c97c..00000000000 --- a/Libraries/LibCrypto/Checksum/CRC8.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2023, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace Crypto::Checksum { - -// A generic 8-bit Cyclic Redundancy Check. -// Note that as opposed to CRC32, this class operates with MSB first, so the polynomial must not be reversed. -// For example, the polynomial x⁸ + x² + x + 1 is represented as 0x07 and not 0xE0. -template -class CRC8 : public ChecksumFunction { -public: - // This is a big endian table, while CRC-32 uses a little endian table. - static constexpr auto generate_table() - { - Array data {}; - u8 value = 0x80; - auto i = 1u; - do { - if ((value & 0x80) != 0) { - value = polynomial ^ (value << 1); - } else { - value = value << 1; - } - - for (auto j = 0u; j < i; ++j) { - data[i + j] = value ^ data[j]; - } - i <<= 1; - } while (i < 256); - return data; - } - - static constexpr auto table = generate_table(); - - virtual ~CRC8() = default; - - CRC8() = default; - CRC8(ReadonlyBytes data) - { - update(data); - } - - CRC8(u8 initial_state, ReadonlyBytes data) - : m_state(initial_state) - { - update(data); - } - - // FIXME: This implementation is naive and slow. - // Figure out how to adopt the slicing-by-8 algorithm (see CRC32) for 8 bit polynomials. - virtual void update(ReadonlyBytes data) override - { - for (size_t i = 0; i < data.size(); i++) { - size_t table_index = (m_state ^ data.at(i)) & 0xFF; - m_state = (table[table_index] ^ (static_cast(m_state) << 8)) & 0xFF; - } - } - - virtual u8 digest() override - { - return m_state; - } - -private: - u8 m_state { 0 }; -}; - -} diff --git a/Tests/LibCrypto/TestChecksum.cpp b/Tests/LibCrypto/TestChecksum.cpp index 2c5574266b3..7db17ae5201 100644 --- a/Tests/LibCrypto/TestChecksum.cpp +++ b/Tests/LibCrypto/TestChecksum.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include @@ -36,15 +35,3 @@ TEST_CASE(test_cksum_atomic_digest) compare(digest, 0x2D65C7E0); } - -TEST_CASE(test_crc32) -{ - auto do_test = [](ReadonlyBytes input, u32 expected_result) { - auto digest = Crypto::Checksum::CRC32(input).digest(); - EXPECT_EQ(digest, expected_result); - }; - - do_test(""sv.bytes(), 0x0); - do_test("The quick brown fox jumps over the lazy dog"sv.bytes(), 0x414FA339); - do_test("various CRC algorithms input data"sv.bytes(), 0x9BD366AE); -} From f3631d6517a71c0bfee06821fd88593c7b7058b2 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:56:12 +0100 Subject: [PATCH 066/141] LibCrypto: Remove unused `cksum` class --- Libraries/LibCrypto/CMakeLists.txt | 1 - Libraries/LibCrypto/Checksum/cksum.cpp | 88 -------------------------- Libraries/LibCrypto/Checksum/cksum.h | 31 --------- Tests/LibCrypto/CMakeLists.txt | 1 - Tests/LibCrypto/TestChecksum.cpp | 37 ----------- 5 files changed, 158 deletions(-) delete mode 100644 Libraries/LibCrypto/Checksum/cksum.cpp delete mode 100644 Libraries/LibCrypto/Checksum/cksum.h delete mode 100644 Tests/LibCrypto/TestChecksum.cpp diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index 5bb84381955..eef134df949 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -17,7 +17,6 @@ set(SOURCES BigInt/SignedBigInteger.cpp BigInt/UnsignedBigInteger.cpp Certificate/Certificate.cpp - Checksum/cksum.cpp Cipher/AES.cpp Curves/EdwardsCurve.cpp Curves/SECPxxxr1.cpp diff --git a/Libraries/LibCrypto/Checksum/cksum.cpp b/Libraries/LibCrypto/Checksum/cksum.cpp deleted file mode 100644 index e519fa5768f..00000000000 --- a/Libraries/LibCrypto/Checksum/cksum.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2024, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Crypto::Checksum { - -static constexpr u32 table[256] = { - 0x00000000, - 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, - 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, - 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, - 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, - 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, - 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, - 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, - 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, - 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, - 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, - 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, - 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, - 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, - 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, - 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, - 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, - 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, - 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, - 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, - 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, - 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, - 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, - 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, - 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, - 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, - 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, - 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, - 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, - 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, - 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, - 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, - 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, - 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, - 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, - 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, - 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, - 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, - 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, - 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, - 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, - 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, - 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, - 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, - 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, - 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, - 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, - 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, - 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, - 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, - 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, - 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 -}; - -void cksum::update(ReadonlyBytes data) -{ - for (u8 byte : data) - m_state = (m_state << 8) ^ table[((m_state >> 24) ^ byte) & 0xFF]; - - m_size += data.size(); -} - -u32 cksum::digest() -{ - u32 size = m_size; - u32 state = m_state; - - while (size) { - state = (state << 8) ^ table[((state >> 24) ^ size) & 0xFF]; - size >>= 8; - } - - state = ~state & 0xFFFFFFFF; - return state; -} - -} diff --git a/Libraries/LibCrypto/Checksum/cksum.h b/Libraries/LibCrypto/Checksum/cksum.h deleted file mode 100644 index 6ffb86dfd23..00000000000 --- a/Libraries/LibCrypto/Checksum/cksum.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Crypto::Checksum { - -class cksum : public ChecksumFunction { -public: - cksum() = default; - cksum(ReadonlyBytes data) - { - update(data); - } - - virtual void update(ReadonlyBytes data) override; - virtual u32 digest() override; - -private: - u32 m_state { 0 }; - off_t m_size { 0 }; -}; - -} diff --git a/Tests/LibCrypto/CMakeLists.txt b/Tests/LibCrypto/CMakeLists.txt index 10976baac6f..d9cab3629d3 100644 --- a/Tests/LibCrypto/CMakeLists.txt +++ b/Tests/LibCrypto/CMakeLists.txt @@ -3,7 +3,6 @@ set(TEST_SOURCES TestASN1.cpp TestBigFraction.cpp TestBigInteger.cpp - TestChecksum.cpp TestCurves.cpp TestEd25519.cpp TestEd448.cpp diff --git a/Tests/LibCrypto/TestChecksum.cpp b/Tests/LibCrypto/TestChecksum.cpp deleted file mode 100644 index 7db17ae5201..00000000000 --- a/Tests/LibCrypto/TestChecksum.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021, Peter Bocan - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -TEST_CASE(test_cksum) -{ - auto do_test = [](ReadonlyBytes input, u32 expected_result) { - auto digest = Crypto::Checksum::cksum(input).digest(); - EXPECT_EQ(digest, expected_result); - }; - - do_test(""sv.bytes(), 0xFFFFFFFF); - do_test("The quick brown fox jumps over the lazy dog"sv.bytes(), 0x7BAB9CE8); - do_test("various CRC algorithms input data"sv.bytes(), 0xEFB5CA4F); -} - -TEST_CASE(test_cksum_atomic_digest) -{ - auto compare = [](u32 digest, u32 expected_result) { - EXPECT_EQ(digest, expected_result); - }; - - Crypto::Checksum::cksum cksum; - - cksum.update("Well"sv.bytes()); - cksum.update(" hello "sv.bytes()); - cksum.digest(); - cksum.update("friends"sv.bytes()); - auto digest = cksum.digest(); - - compare(digest, 0x2D65C7E0); -} From 2b3934e34e4c1215b04983a1b7eb076398169b2f Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:56:54 +0100 Subject: [PATCH 067/141] LibCrypto: Remove leftover checksum helpers --- .../LibCrypto/Checksum/ChecksumFunction.h | 25 ------ .../LibCrypto/Checksum/ChecksummingStream.h | 81 ------------------- 2 files changed, 106 deletions(-) delete mode 100644 Libraries/LibCrypto/Checksum/ChecksumFunction.h delete mode 100644 Libraries/LibCrypto/Checksum/ChecksummingStream.h diff --git a/Libraries/LibCrypto/Checksum/ChecksumFunction.h b/Libraries/LibCrypto/Checksum/ChecksumFunction.h deleted file mode 100644 index 67b1c53b7ce..00000000000 --- a/Libraries/LibCrypto/Checksum/ChecksumFunction.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Crypto::Checksum { - -template -class ChecksumFunction { -public: - using ChecksumType = ChecksumT; - - virtual void update(ReadonlyBytes data) = 0; - virtual ChecksumType digest() = 0; - -protected: - virtual ~ChecksumFunction() = default; -}; - -} diff --git a/Libraries/LibCrypto/Checksum/ChecksummingStream.h b/Libraries/LibCrypto/Checksum/ChecksummingStream.h deleted file mode 100644 index 5c71d00d29a..00000000000 --- a/Libraries/LibCrypto/Checksum/ChecksummingStream.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Crypto::Checksum { - -// A stream wrapper type which passes all read and written data through a checksum function. -template -requires( - IsBaseOf, ChecksumFunctionType>, - // Require checksum function to be constructible without arguments, since we have no initial data. - requires() { - ChecksumFunctionType {}; - }) -class ChecksummingStream : public Stream { -public: - virtual ~ChecksummingStream() = default; - - ChecksummingStream(MaybeOwned stream) - : m_stream(move(stream)) - { - } - - virtual ErrorOr read_some(Bytes bytes) override - { - auto const written_bytes = TRY(m_stream->read_some(bytes)); - update(written_bytes); - return written_bytes; - } - - virtual ErrorOr read_until_filled(Bytes bytes) override - { - TRY(m_stream->read_until_filled(bytes)); - update(bytes); - return {}; - } - - virtual ErrorOr write_some(ReadonlyBytes bytes) override - { - auto bytes_written = TRY(m_stream->write_some(bytes)); - // Only update with the bytes that were actually written - update(bytes.trim(bytes_written)); - return bytes_written; - } - - virtual ErrorOr write_until_depleted(ReadonlyBytes bytes) override - { - update(bytes); - return m_stream->write_until_depleted(bytes); - } - - virtual bool is_eof() const override { return m_stream->is_eof(); } - virtual bool is_open() const override { return m_stream->is_open(); } - virtual void close() override { m_stream->close(); } - - ChecksumType digest() - { - return m_checksum.digest(); - } - -private: - ALWAYS_INLINE void update(ReadonlyBytes bytes) - { - m_checksum.update(bytes); - } - - MaybeOwned m_stream; - ChecksumFunctionType m_checksum {}; -}; - -} From e02521911a65b32c5256475f3ed99fb960a83001 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:57:52 +0100 Subject: [PATCH 068/141] LibWeb: Refactor `{Dec,C}ompressionStream` to handle split chunks --- .../LibWeb/Compression/CompressionStream.cpp | 23 +++-- .../Compression/DecompressionStream.cpp | 54 ++++++----- .../LibWeb/Compression/DecompressionStream.h | 2 - ...ecompression-split-chunk.tentative.any.txt | 94 +++++++++---------- 4 files changed, 90 insertions(+), 83 deletions(-) diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index a18224c36c6..c6d12fb809e 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -113,21 +114,22 @@ WebIDL::ExceptionOr CompressionStream::compress_and_enqueue_chunk(JS::Valu return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Chunk is not a BufferSource type"sv }; // 2. Let buffer be the result of compressing chunk with cs's format and context. - auto buffer = [&]() -> ErrorOr { + auto maybe_buffer = [&]() -> ErrorOr { if (auto buffer = WebIDL::underlying_buffer_source(chunk.as_object())) return compress(buffer->buffer(), Finish::No); return ByteBuffer {}; }(); + if (maybe_buffer.is_error()) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress chunk: {}", maybe_buffer.error())) }; - if (buffer.is_error()) - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress chunk: {}", buffer.error())) }; + auto buffer = maybe_buffer.release_value(); // 3. If buffer is empty, return. - if (buffer.value().is_empty()) + if (buffer.is_empty()) return {}; // 4. Split buffer into one or more non-empty pieces and convert them into Uint8Arrays. - auto array_buffer = JS::ArrayBuffer::create(realm, buffer.release_value()); + auto array_buffer = JS::ArrayBuffer::create(realm, move(buffer)); auto array = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer); // 5. For each Uint8Array array, enqueue array in cs's transform. @@ -141,17 +143,18 @@ WebIDL::ExceptionOr CompressionStream::compress_flush_and_enqueue() auto& realm = this->realm(); // 1. Let buffer be the result of compressing an empty input with cs's format and context, with the finish flag. - auto buffer = compress({}, Finish::Yes); + auto maybe_buffer = compress({}, Finish::Yes); + if (maybe_buffer.is_error()) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress flush: {}", maybe_buffer.error())) }; - if (buffer.is_error()) - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress flush: {}", buffer.error())) }; + auto buffer = maybe_buffer.release_value(); // 2. If buffer is empty, return. - if (buffer.value().is_empty()) + if (buffer.is_empty()) return {}; // 3. Split buffer into one or more non-empty pieces and convert them into Uint8Arrays. - auto array_buffer = JS::ArrayBuffer::create(realm, buffer.release_value()); + auto array_buffer = JS::ArrayBuffer::create(realm, move(buffer)); auto array = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer); // 4. For each Uint8Array array, enqueue array in cs's transform. diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index 8739417afac..bc68d40c761 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -115,21 +116,30 @@ WebIDL::ExceptionOr DecompressionStream::decompress_and_enqueue_chunk(JS:: // 2. Let buffer be the result of decompressing chunk with ds's format and context. If this results in an error, // then throw a TypeError. - auto buffer = [&]() -> ErrorOr { - if (auto buffer = WebIDL::underlying_buffer_source(chunk.as_object())) - return decompress(buffer->buffer()); - return ByteBuffer {}; - }(); + auto maybe_buffer = [&]() -> ErrorOr { + auto chunk_buffer = WebIDL::underlying_buffer_source(chunk.as_object()); + if (!chunk_buffer) + return ByteBuffer {}; - if (buffer.is_error()) - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to decompress chunk: {}", buffer.error())) }; + TRY(m_input_stream->write_until_depleted(chunk_buffer->buffer())); + + auto decompressed = TRY(ByteBuffer::create_uninitialized(4096)); + auto size = TRY(m_decompressor.visit([&](auto const& decompressor) -> ErrorOr { + return TRY(decompressor->read_some(decompressed.bytes())).size(); + })); + return decompressed.slice(0, size); + }(); + if (maybe_buffer.is_error()) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to decompress chunk: {}", maybe_buffer.error())) }; + + auto buffer = maybe_buffer.release_value(); // 3. If buffer is empty, return. - if (buffer.value().is_empty()) + if (buffer.is_empty()) return {}; // 4. Split buffer into one or more non-empty pieces and convert them into Uint8Arrays. - auto array_buffer = JS::ArrayBuffer::create(realm, buffer.release_value()); + auto array_buffer = JS::ArrayBuffer::create(realm, move(buffer)); auto array = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer); // 5. For each Uint8Array array, enqueue array in ds's transform. @@ -143,19 +153,24 @@ WebIDL::ExceptionOr DecompressionStream::decompress_flush_and_enqueue() auto& realm = this->realm(); // 1. Let buffer be the result of decompressing an empty input with ds's format and context, with the finish flag. - auto buffer = decompress({}); + auto maybe_buffer = m_decompressor.visit([&](auto const& decompressor) -> ErrorOr { + return TRY(decompressor->read_until_eof()); + }); + if (maybe_buffer.is_error()) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress flush: {}", maybe_buffer.error())) }; - if (buffer.is_error()) - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress flush: {}", buffer.error())) }; + auto buffer = maybe_buffer.release_value(); - // FIXME: 2. If the end of the compressed input has not been reached, then throw a TypeError. + // 2. If the end of the compressed input has not been reached, then throw a TypeError. + if (m_decompressor.visit([](auto const& decompressor) { return !decompressor->is_eof(); })) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "End of compressed input has not been reached"sv }; // 3. If buffer is empty, return. - if (buffer.value().is_empty()) + if (buffer.is_empty()) return {}; // 4. Split buffer into one or more non-empty pieces and convert them into Uint8Arrays. - auto array_buffer = JS::ArrayBuffer::create(realm, buffer.release_value()); + auto array_buffer = JS::ArrayBuffer::create(realm, move(buffer)); auto array = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer); // 5. For each Uint8Array array, enqueue array in ds's transform. @@ -163,13 +178,4 @@ WebIDL::ExceptionOr DecompressionStream::decompress_flush_and_enqueue() return {}; } -ErrorOr DecompressionStream::decompress(ReadonlyBytes bytes) -{ - TRY(m_input_stream->write_until_depleted(bytes)); - - return TRY(m_decompressor.visit([&](auto const& decompressor) { - return decompressor->read_until_eof(); - })); -} - } diff --git a/Libraries/LibWeb/Compression/DecompressionStream.h b/Libraries/LibWeb/Compression/DecompressionStream.h index 88ec91ae723..b02074f0cb5 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.h +++ b/Libraries/LibWeb/Compression/DecompressionStream.h @@ -44,8 +44,6 @@ private: WebIDL::ExceptionOr decompress_and_enqueue_chunk(JS::Value); WebIDL::ExceptionOr decompress_flush_and_enqueue(); - ErrorOr decompress(ReadonlyBytes); - Decompressor m_decompressor; NonnullOwnPtr m_input_stream; }; diff --git a/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt b/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt index eb7282ce1e1..a30d1b99ee6 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/compression/decompression-split-chunk.tentative.any.txt @@ -1,50 +1,50 @@ -Harness status: Error +Harness status: OK Found 45 tests -45 Fail -Fail decompressing splitted chunk into pieces of size 1 should work in deflate -Fail decompressing splitted chunk into pieces of size 1 should work in gzip -Fail decompressing splitted chunk into pieces of size 1 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 2 should work in deflate -Fail decompressing splitted chunk into pieces of size 2 should work in gzip -Fail decompressing splitted chunk into pieces of size 2 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 3 should work in deflate -Fail decompressing splitted chunk into pieces of size 3 should work in gzip -Fail decompressing splitted chunk into pieces of size 3 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 4 should work in deflate -Fail decompressing splitted chunk into pieces of size 4 should work in gzip -Fail decompressing splitted chunk into pieces of size 4 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 5 should work in deflate -Fail decompressing splitted chunk into pieces of size 5 should work in gzip -Fail decompressing splitted chunk into pieces of size 5 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 6 should work in deflate -Fail decompressing splitted chunk into pieces of size 6 should work in gzip -Fail decompressing splitted chunk into pieces of size 6 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 7 should work in deflate -Fail decompressing splitted chunk into pieces of size 7 should work in gzip -Fail decompressing splitted chunk into pieces of size 7 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 8 should work in deflate -Fail decompressing splitted chunk into pieces of size 8 should work in gzip -Fail decompressing splitted chunk into pieces of size 8 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 9 should work in deflate -Fail decompressing splitted chunk into pieces of size 9 should work in gzip -Fail decompressing splitted chunk into pieces of size 9 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 10 should work in deflate -Fail decompressing splitted chunk into pieces of size 10 should work in gzip -Fail decompressing splitted chunk into pieces of size 10 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 11 should work in deflate -Fail decompressing splitted chunk into pieces of size 11 should work in gzip -Fail decompressing splitted chunk into pieces of size 11 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 12 should work in deflate -Fail decompressing splitted chunk into pieces of size 12 should work in gzip -Fail decompressing splitted chunk into pieces of size 12 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 13 should work in deflate -Fail decompressing splitted chunk into pieces of size 13 should work in gzip -Fail decompressing splitted chunk into pieces of size 13 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 14 should work in deflate -Fail decompressing splitted chunk into pieces of size 14 should work in gzip -Fail decompressing splitted chunk into pieces of size 14 should work in deflate-raw -Fail decompressing splitted chunk into pieces of size 15 should work in deflate -Fail decompressing splitted chunk into pieces of size 15 should work in gzip -Fail decompressing splitted chunk into pieces of size 15 should work in deflate-raw \ No newline at end of file +45 Pass +Pass decompressing splitted chunk into pieces of size 1 should work in deflate +Pass decompressing splitted chunk into pieces of size 1 should work in gzip +Pass decompressing splitted chunk into pieces of size 1 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 2 should work in deflate +Pass decompressing splitted chunk into pieces of size 2 should work in gzip +Pass decompressing splitted chunk into pieces of size 2 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 3 should work in deflate +Pass decompressing splitted chunk into pieces of size 3 should work in gzip +Pass decompressing splitted chunk into pieces of size 3 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 4 should work in deflate +Pass decompressing splitted chunk into pieces of size 4 should work in gzip +Pass decompressing splitted chunk into pieces of size 4 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 5 should work in deflate +Pass decompressing splitted chunk into pieces of size 5 should work in gzip +Pass decompressing splitted chunk into pieces of size 5 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 6 should work in deflate +Pass decompressing splitted chunk into pieces of size 6 should work in gzip +Pass decompressing splitted chunk into pieces of size 6 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 7 should work in deflate +Pass decompressing splitted chunk into pieces of size 7 should work in gzip +Pass decompressing splitted chunk into pieces of size 7 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 8 should work in deflate +Pass decompressing splitted chunk into pieces of size 8 should work in gzip +Pass decompressing splitted chunk into pieces of size 8 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 9 should work in deflate +Pass decompressing splitted chunk into pieces of size 9 should work in gzip +Pass decompressing splitted chunk into pieces of size 9 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 10 should work in deflate +Pass decompressing splitted chunk into pieces of size 10 should work in gzip +Pass decompressing splitted chunk into pieces of size 10 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 11 should work in deflate +Pass decompressing splitted chunk into pieces of size 11 should work in gzip +Pass decompressing splitted chunk into pieces of size 11 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 12 should work in deflate +Pass decompressing splitted chunk into pieces of size 12 should work in gzip +Pass decompressing splitted chunk into pieces of size 12 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 13 should work in deflate +Pass decompressing splitted chunk into pieces of size 13 should work in gzip +Pass decompressing splitted chunk into pieces of size 13 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 14 should work in deflate +Pass decompressing splitted chunk into pieces of size 14 should work in gzip +Pass decompressing splitted chunk into pieces of size 14 should work in deflate-raw +Pass decompressing splitted chunk into pieces of size 15 should work in deflate +Pass decompressing splitted chunk into pieces of size 15 should work in gzip +Pass decompressing splitted chunk into pieces of size 15 should work in deflate-raw \ No newline at end of file From 033ba43faff98d4d5f643c0e54b96eaa798f2e2b Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 19:26:17 +0100 Subject: [PATCH 069/141] LibWeb: Use `get_buffer_source_copy` for getting the actual byte buffer `WebIDL::underlying_buffer_source` gets the underlying buffer source which is not resized according to, for example, `subarray`. --- Libraries/LibWeb/Compression/CompressionStream.cpp | 5 ++--- Libraries/LibWeb/Compression/DecompressionStream.cpp | 7 ++----- .../compression/compression-large-flush-output.any.txt | 8 ++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index c6d12fb809e..d2679dd56cd 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -115,9 +115,8 @@ WebIDL::ExceptionOr CompressionStream::compress_and_enqueue_chunk(JS::Valu // 2. Let buffer be the result of compressing chunk with cs's format and context. auto maybe_buffer = [&]() -> ErrorOr { - if (auto buffer = WebIDL::underlying_buffer_source(chunk.as_object())) - return compress(buffer->buffer(), Finish::No); - return ByteBuffer {}; + auto chunk_buffer = TRY(WebIDL::get_buffer_source_copy(chunk.as_object())); + return compress(move(chunk_buffer), Finish::No); }(); if (maybe_buffer.is_error()) return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to compress chunk: {}", maybe_buffer.error())) }; diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index bc68d40c761..4de816e114e 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -117,11 +117,8 @@ WebIDL::ExceptionOr DecompressionStream::decompress_and_enqueue_chunk(JS:: // 2. Let buffer be the result of decompressing chunk with ds's format and context. If this results in an error, // then throw a TypeError. auto maybe_buffer = [&]() -> ErrorOr { - auto chunk_buffer = WebIDL::underlying_buffer_source(chunk.as_object()); - if (!chunk_buffer) - return ByteBuffer {}; - - TRY(m_input_stream->write_until_depleted(chunk_buffer->buffer())); + auto chunk_buffer = TRY(WebIDL::get_buffer_source_copy(chunk.as_object())); + TRY(m_input_stream->write_until_depleted(move(chunk_buffer))); auto decompressed = TRY(ByteBuffer::create_uninitialized(4096)); auto size = TRY(m_decompressor.visit([&](auto const& decompressor) -> ErrorOr { diff --git a/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt index a3ab97e1de4..c4769acfffc 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/compression/compression-large-flush-output.any.txt @@ -2,7 +2,7 @@ Harness status: OK Found 3 tests -3 Fail -Fail deflate compression with large flush output -Fail gzip compression with large flush output -Fail deflate-raw compression with large flush output \ No newline at end of file +3 Pass +Pass deflate compression with large flush output +Pass gzip compression with large flush output +Pass deflate-raw compression with large flush output \ No newline at end of file From cfeb916e61d797bd2a410cc30acc199c6aa1670c Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 19:23:39 +0100 Subject: [PATCH 070/141] LibWeb: Remove unused `underlying_buffer_source` function This function is not used anywhere and will most likely end up causing problems so just remove it. --- Libraries/LibWeb/WebIDL/AbstractOperations.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Libraries/LibWeb/WebIDL/AbstractOperations.cpp b/Libraries/LibWeb/WebIDL/AbstractOperations.cpp index f77e6da6d6f..31ed6824615 100644 --- a/Libraries/LibWeb/WebIDL/AbstractOperations.cpp +++ b/Libraries/LibWeb/WebIDL/AbstractOperations.cpp @@ -33,18 +33,6 @@ bool is_buffer_source_type(JS::Value value) return is(object) || is(object) || is(object); } -GC::Ptr underlying_buffer_source(JS::Object& buffer_source) -{ - if (is(buffer_source)) - return static_cast(buffer_source).viewed_array_buffer(); - if (is(buffer_source)) - return static_cast(buffer_source).viewed_array_buffer(); - if (is(buffer_source)) - return static_cast(buffer_source); - - VERIFY_NOT_REACHED(); -} - // https://webidl.spec.whatwg.org/#dfn-get-buffer-source-copy ErrorOr get_buffer_source_copy(JS::Object const& buffer_source) { From c2e21d33eb1233a7fb45c850a000b1cb146baee1 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Wed, 19 Mar 2025 10:55:22 +0100 Subject: [PATCH 071/141] LibWeb: Do not calculate corner radii if no radius is set The `as_corner()` and `floored_device_pixels()` functions popped up frequently in profiles when selecting text on some Tweakers.net pages. For every corner we're performing multiple device pixel calculations regardless of whether any radius was actually set. Add an early return if no radius is set. On my machine this reduces the time taken in both `as_corner()` and `floored_device_pixels()` by 46% (63% fewer calls). --- Libraries/LibWeb/Painting/BorderRadiiData.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/LibWeb/Painting/BorderRadiiData.h b/Libraries/LibWeb/Painting/BorderRadiiData.h index 54eb71aab7b..fa69b77918d 100644 --- a/Libraries/LibWeb/Painting/BorderRadiiData.h +++ b/Libraries/LibWeb/Painting/BorderRadiiData.h @@ -93,6 +93,8 @@ struct BorderRadiiData { inline CornerRadii as_corners(PaintContext const& context) const { + if (!has_any_radius()) + return {}; return CornerRadii { top_left.as_corner(context), top_right.as_corner(context), From dea5fde3f924884ae55cbeed03c3628d2d01cf2d Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Wed, 19 Mar 2025 11:24:58 +0000 Subject: [PATCH 072/141] LibWeb/HTML: Fix source insertion/removal steps Corresponds to https://github.com/whatwg/html/commit/be5f269ca89278bb23801d584ea8ee43bbf6449b We don't yet implement "moving steps" so that's left for when we do. --- Libraries/LibWeb/HTML/HTMLSourceElement.cpp | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLSourceElement.cpp b/Libraries/LibWeb/HTML/HTMLSourceElement.cpp index 721091c0a75..b654b273d8a 100644 --- a/Libraries/LibWeb/HTML/HTMLSourceElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLSourceElement.cpp @@ -27,33 +27,35 @@ void HTMLSourceElement::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLSourceElement); } -// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:the-source-element-15 +// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:html-element-insertion-steps void HTMLSourceElement::inserted() { // The source HTML element insertion steps, given insertedNode, are: Base::inserted(); - // 1. If insertedNode's parent is a media element that has no src attribute and whose networkState has the value - // NETWORK_EMPTY, then invoke that media element's resource selection algorithm. - if (is(parent())) { - auto& media_element = static_cast(*parent()); + // 1. Let parent be insertedNode's parent. + auto* parent = this->parent(); - if (!media_element.has_attribute(HTML::AttributeNames::src) && media_element.network_state() == HTMLMediaElement::NetworkState::Empty) - media_element.select_resource().release_value_but_fixme_should_propagate_errors(); + // 2. If parent is a media element that has no src attribute and whose networkState has the value NETWORK_EMPTY, + // then invoke that media element's resource selection algorithm. + if (auto* media_element = as_if(parent); media_element + && !media_element->has_attribute(HTML::AttributeNames::src) + && media_element->network_state() == HTMLMediaElement::NetworkState::Empty) { + media_element->select_resource().release_value_but_fixme_should_propagate_errors(); } - // FIXME: 2. If insertedNode's next sibling is an img element and its parent is a picture element, then, count this as a - // relevant mutation for the img element. + // FIXME: 3. If parent is a picture element, then for each child of parent's children, if child is an img element, then + // count this as a relevant mutation for child. } -// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:the-source-element-16 +// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:html-element-removing-steps void HTMLSourceElement::removed_from(DOM::Node* old_parent, DOM::Node& old_root) { // The source HTML element removing steps, given removedNode and oldParent, are: Base::removed_from(old_parent, old_root); - // FIXME: 1. If removedNode's next sibling was an img element and oldParent is a picture element, then, count this as a - // relevant mutation for the img element. + // FIXME: 1. If oldParent is a picture element, then for each child of oldParent's children, if child is an img + // element, then count this as a relevant mutation for child. } } From 3425aa0737e273bfa922c44ca5a40d549e2c5e93 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 17 Mar 2025 16:00:12 +0000 Subject: [PATCH 073/141] LibWeb/DOM: Introduce an ElementReference type We have the "Element, but also maybe a pseudo-element of it" concept in a lot of places, so let's wrap it up in a single type to make it easier to deal with. --- Libraries/LibWeb/DOM/ElementReference.h | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Libraries/LibWeb/DOM/ElementReference.h diff --git a/Libraries/LibWeb/DOM/ElementReference.h b/Libraries/LibWeb/DOM/ElementReference.h new file mode 100644 index 00000000000..d8c23b38f6b --- /dev/null +++ b/Libraries/LibWeb/DOM/ElementReference.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::DOM { + +class ElementReference { +public: + ElementReference(GC::Ref element, Optional pseudo_element = {}) + : m_element(element) + , m_pseudo_element(move(pseudo_element)) + { + } + + Element& element() { return m_element; } + Element const& element() const { return m_element; } + Optional pseudo_element() const { return m_pseudo_element; } + + void visit(GC::Cell::Visitor& visitor) const + { + visitor.visit(m_element); + } + +private: + GC::Ref m_element; + Optional m_pseudo_element; +}; + +} From f0d198ca3f4866ccf406c3320d2fa5326bf6fe8c Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 15:05:42 +0000 Subject: [PATCH 074/141] LibWeb/CSS: Move CSSStyleDeclaration subclasses' fields into it The spec defines several properties on the declaration which we previously made virtual or stored on subclasses. This commit moves these into CSSStyleDeclaration itself, and updates some of the naming. --- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 73 ++++++++----------- Libraries/LibWeb/CSS/CSSStyleDeclaration.h | 65 ++++++++++++----- .../CSS/ResolvedCSSStyleDeclaration.cpp | 48 ++++++------ .../LibWeb/CSS/ResolvedCSSStyleDeclaration.h | 7 -- 4 files changed, 101 insertions(+), 92 deletions(-) diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 459f0a72a60..0f97ffa29f5 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -25,8 +25,10 @@ GC_DEFINE_ALLOCATOR(CSSStyleDeclaration); GC_DEFINE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); GC_DEFINE_ALLOCATOR(ElementInlineCSSStyleDeclaration); -CSSStyleDeclaration::CSSStyleDeclaration(JS::Realm& realm) +CSSStyleDeclaration::CSSStyleDeclaration(JS::Realm& realm, Computed computed, Readonly readonly) : PlatformObject(realm) + , m_computed(computed == Computed::Yes) + , m_readonly(readonly == Readonly::Yes) { m_legacy_platform_object_flags = LegacyPlatformObjectFlags { .supports_indexed_properties = true, @@ -39,27 +41,21 @@ void CSSStyleDeclaration::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleDeclaration); } -GC::Ptr CSSStyleDeclaration::parent_rule() const -{ - return nullptr; -} - GC::Ref PropertyOwningCSSStyleDeclaration::create(JS::Realm& realm, Vector properties, HashMap custom_properties) { return realm.create(realm, move(properties), move(custom_properties)); } PropertyOwningCSSStyleDeclaration::PropertyOwningCSSStyleDeclaration(JS::Realm& realm, Vector properties, HashMap custom_properties) - : CSSStyleDeclaration(realm) + : CSSStyleDeclaration(realm, Computed::No, Readonly::No) , m_properties(move(properties)) , m_custom_properties(move(custom_properties)) { } -void PropertyOwningCSSStyleDeclaration::visit_edges(Cell::Visitor& visitor) +void PropertyOwningCSSStyleDeclaration::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); - visitor.visit(m_parent_rule); for (auto& property : m_properties) { if (property.value->is_image()) property.value->as_image().visit_edges(visitor); @@ -81,14 +77,8 @@ GC::Ref ElementInlineCSSStyleDeclaration::crea ElementInlineCSSStyleDeclaration::ElementInlineCSSStyleDeclaration(DOM::Element& element, Vector properties, HashMap custom_properties) : PropertyOwningCSSStyleDeclaration(element.realm(), move(properties), move(custom_properties)) - , m_element(element.make_weak_ptr()) { -} - -void ElementInlineCSSStyleDeclaration::visit_edges(Cell::Visitor& visitor) -{ - Base::visit_edges(visitor); - visitor.visit(m_element); + set_owner_node(DOM::ElementReference { element }); } size_t PropertyOwningCSSStyleDeclaration::length() const @@ -132,8 +122,8 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(String return {}; // 5. Let component value list be the result of parsing value for property property. - auto component_value_list = is(this) - ? parse_css_value(CSS::Parser::ParsingParams { static_cast(*this).element()->document() }, value, property_id) + auto component_value_list = owner_node().has_value() + ? parse_css_value(CSS::Parser::ParsingParams { owner_node()->element().document() }, value, property_id) : parse_css_value(CSS::Parser::ParsingParams {}, value, property_id); // 6. If component value list is null, then return. @@ -223,21 +213,21 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(S void ElementInlineCSSStyleDeclaration::update_style_attribute() { // 1. Assert: declaration block’s computed flag is unset. - // NOTE: Unnecessary, only relevant for ResolvedCSSStyleDeclaration. + VERIFY(!is_computed()); // 2. Let owner node be declaration block’s owner node. // 3. If owner node is null, then return. - if (!m_element) + if (!owner_node().has_value()) return; // 4. Set declaration block’s updating flag. - m_updating = true; + set_is_updating(true); // 5. Set an attribute value for owner node using "style" and the result of serializing declaration block. - MUST(m_element->set_attribute(HTML::AttributeNames::style, serialized())); + MUST(owner_node()->element().set_attribute(HTML::AttributeNames::style, serialized())); // 6. Unset declaration block’s updating flag. - m_updating = false; + set_is_updating(false); } // https://drafts.csswg.org/cssom/#set-a-css-declaration @@ -445,8 +435,8 @@ String CSSStyleDeclaration::get_property_value(StringView property_name) const auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes())); if (maybe_custom_property.has_value()) { return maybe_custom_property.value().value->to_string( - computed_flag() ? CSSStyleValue::SerializationMode::ResolvedValue - : CSSStyleValue::SerializationMode::Normal); + is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue + : CSSStyleValue::SerializationMode::Normal); } return {}; } @@ -455,8 +445,8 @@ String CSSStyleDeclaration::get_property_value(StringView property_name) const if (!maybe_property.has_value()) return {}; return maybe_property->value->to_string( - computed_flag() ? CSSStyleValue::SerializationMode::ResolvedValue - : CSSStyleValue::SerializationMode::Normal); + is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue + : CSSStyleValue::SerializationMode::Normal); } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority @@ -520,6 +510,14 @@ Optional CSSStyleDeclaration::item_value(size_t index) const return JS::PrimitiveString::create(vm(), value); } +void CSSStyleDeclaration::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_parent_rule); + if (m_owner_node.has_value()) + m_owner_node->visit(visitor); +} + // https://www.w3.org/TR/cssom/#serialize-a-css-declaration static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) { @@ -663,13 +661,14 @@ void PropertyOwningCSSStyleDeclaration::set_the_declarations(Vectordocument()), css_text, *m_element.ptr()); + auto style = parse_css_style_attribute(CSS::Parser::ParsingParams(element->element().document()), css_text, element->element()); set_the_declarations(style->properties(), style->custom_properties()); } @@ -677,8 +676,8 @@ void ElementInlineCSSStyleDeclaration::set_declarations_from_text(StringView css WebIDL::ExceptionOr ElementInlineCSSStyleDeclaration::set_css_text(StringView css_text) { // FIXME: What do we do if the element is null? - if (!m_element) { - dbgln("FIXME: Returning from ElementInlineCSSStyleDeclaration::set_css_text as m_element is null."); + if (!owner_node().has_value()) { + dbgln("FIXME: Returning from ElementInlineCSSStyleDeclaration::set_css_text as element is null."); return {}; } @@ -695,14 +694,4 @@ WebIDL::ExceptionOr ElementInlineCSSStyleDeclaration::set_css_text(StringV return {}; } -GC::Ptr PropertyOwningCSSStyleDeclaration::parent_rule() const -{ - return m_parent_rule; -} - -void PropertyOwningCSSStyleDeclaration::set_parent_rule(GC::Ref rule) -{ - m_parent_rule = rule; -} - } diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index e93db085ead..08b78acdd71 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2023, Andreas Kling + * Copyright (c) 2024-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,9 +13,11 @@ #include #include #include +#include namespace Web::CSS { +// https://drafts.csswg.org/cssom/#css-declaration-blocks class CSSStyleDeclaration : public Bindings::PlatformObject , public Bindings::GeneratedCSSStyleProperties { @@ -48,13 +51,36 @@ public: virtual String serialized() const = 0; - virtual GC::Ptr parent_rule() const; - // https://drafts.csswg.org/cssom/#cssstyledeclaration-computed-flag - [[nodiscard]] virtual bool computed_flag() const { return false; } + [[nodiscard]] bool is_computed() const { return m_computed; } + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-readonly-flag + [[nodiscard]] bool is_readonly() const { return m_readonly; } + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-parent-css-rule + GC::Ptr parent_rule() const { return m_parent_rule; } + void set_parent_rule(GC::Ptr parent) { m_parent_rule = parent; } + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-owner-node + Optional owner_node() const { return m_owner_node; } + void set_owner_node(Optional owner_node) { m_owner_node = move(owner_node); } + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-updating-flag + [[nodiscard]] bool is_updating() const { return m_updating; } + void set_is_updating(bool value) { m_updating = value; } protected: - explicit CSSStyleDeclaration(JS::Realm&); + enum class Computed : u8 { + No, + Yes, + }; + enum class Readonly : u8 { + No, + Yes, + }; + explicit CSSStyleDeclaration(JS::Realm&, Computed, Readonly); + + virtual void visit_edges(Visitor&) override; virtual CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() override { return *this; } @@ -62,6 +88,21 @@ private: // ^PlatformObject virtual Optional item_value(size_t index) const override; Optional get_property_internal(PropertyID) const; + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-parent-css-rule + GC::Ptr m_parent_rule { nullptr }; + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-owner-node + Optional m_owner_node {}; + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-computed-flag + bool m_computed { false }; + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-readonly-flag + bool m_readonly { false }; + + // https://drafts.csswg.org/cssom/#cssstyledeclaration-updating-flag + bool m_updating { false }; }; class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration { @@ -92,9 +133,6 @@ public: virtual String serialized() const final override; virtual WebIDL::ExceptionOr set_css_text(StringView) override; - virtual GC::Ptr parent_rule() const override; - void set_parent_rule(GC::Ref); - protected: PropertyOwningCSSStyleDeclaration(JS::Realm&, Vector, HashMap); @@ -108,7 +146,6 @@ private: virtual void visit_edges(Cell::Visitor&) override; - GC::Ptr m_parent_rule; Vector m_properties; HashMap m_custom_properties; }; @@ -122,11 +159,6 @@ public: virtual ~ElementInlineCSSStyleDeclaration() override = default; - DOM::Element* element() { return m_element.ptr(); } - const DOM::Element* element() const { return m_element.ptr(); } - - bool is_updating() const { return m_updating; } - void set_declarations_from_text(StringView); virtual WebIDL::ExceptionOr set_css_text(StringView) override; @@ -134,14 +166,7 @@ public: private: ElementInlineCSSStyleDeclaration(DOM::Element&, Vector properties, HashMap custom_properties); - virtual void visit_edges(Cell::Visitor&) override; - virtual void update_style_attribute() override; - - GC::Ptr m_element; - - // https://drafts.csswg.org/cssom/#cssstyledeclaration-updating-flag - bool m_updating { false }; }; } diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index ba618a9f4c7..cba2e8bf139 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -39,16 +39,9 @@ GC::Ref ResolvedCSSStyleDeclaration::create(DOM::El } ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element, Optional pseudo_element) - : CSSStyleDeclaration(element.realm()) - , m_element(element) - , m_pseudo_element(move(pseudo_element)) + : CSSStyleDeclaration(element.realm(), Computed::Yes, Readonly::Yes) { -} - -void ResolvedCSSStyleDeclaration::visit_edges(Cell::Visitor& visitor) -{ - Base::visit_edges(visitor); - visitor.visit(m_element); + set_owner_node(DOM::ElementReference { element, pseudo_element }); } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length @@ -180,6 +173,9 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return {}; }; + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); + auto used_value_for_inset = [&layout_node, used_value_for_property](LengthPercentage const& start_side, LengthPercentage const& end_side, Function&& used_value_getter) -> Optional { if (!layout_node.is_positioned()) return {}; @@ -194,10 +190,10 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return used_value_for_property(move(used_value_getter)); }; - auto get_computed_value = [this](PropertyID property_id) -> auto const& { - if (m_pseudo_element.has_value()) - return m_element->pseudo_element_computed_properties(m_pseudo_element.value())->property(property_id); - return m_element->computed_properties()->property(property_id); + auto get_computed_value = [&element, pseudo_element](PropertyID property_id) -> auto const& { + if (pseudo_element.has_value()) + return element.pseudo_element_computed_properties(pseudo_element.value())->property(property_id); + return element.computed_properties()->property(property_id); }; // A limited number of properties have special rules for producing their "resolved value". @@ -557,15 +553,18 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert Optional ResolvedCSSStyleDeclaration::property(PropertyID property_id) const { + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); + // https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle // NOTE: This is a partial enforcement of step 5 ("If elt is connected, ...") - if (!m_element->is_connected()) + if (!element.is_connected()) return {}; auto get_layout_node = [&]() { - if (m_pseudo_element.has_value()) - return m_element->get_pseudo_element_node(m_pseudo_element.value()); - return m_element->layout_node(); + if (pseudo_element.has_value()) + return element.get_pseudo_element_node(pseudo_element.value()); + return element.layout_node(); }; Layout::NodeWithStyle* layout_node = get_layout_node(); @@ -574,15 +573,15 @@ Optional ResolvedCSSStyleDeclaration::property(PropertyID propert // We may legitimately have no layout node if we're not visible, but this protects against situations // where we're requesting the computed style before layout has happened. if (!layout_node || property_affects_layout(property_id)) { - const_cast(m_element->document()).update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty); + element.document().update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty); layout_node = get_layout_node(); } else { // FIXME: If we had a way to update style for a single element, this would be a good place to use it. - const_cast(m_element->document()).update_style(); + element.document().update_style(); } if (!layout_node) { - auto style = m_element->document().style_computer().compute_style(const_cast(*m_element), m_pseudo_element); + auto style = element.document().style_computer().compute_style(element, pseudo_element); // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. auto const* value = style->maybe_null_property(property_id); @@ -607,11 +606,14 @@ Optional ResolvedCSSStyleDeclaration::property(PropertyID propert Optional ResolvedCSSStyleDeclaration::custom_property(FlyString const& name) const { - const_cast(m_element->document()).update_style(); + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); - auto const* element_to_check = m_element.ptr(); + element.document().update_style(); + + auto const* element_to_check = &element; while (element_to_check) { - if (auto property = element_to_check->custom_properties(m_pseudo_element).get(name); property.has_value()) + if (auto property = element_to_check->custom_properties(pseudo_element).get(name); property.has_value()) return *property; element_to_check = element_to_check->parent_element(); diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h index beb40f92cf2..7743ab02751 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h @@ -36,14 +36,7 @@ public: private: explicit ResolvedCSSStyleDeclaration(DOM::Element&, Optional); - virtual void visit_edges(Cell::Visitor&) override; - - virtual bool computed_flag() const override { return true; } - RefPtr style_value_for_property(Layout::NodeWithStyle const&, PropertyID) const; - - GC::Ref m_element; - Optional m_pseudo_element; }; } From 50455c2f5e4abcfeff9396cd6c4cfd894a7c4e72 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 17 Mar 2025 12:55:21 +0000 Subject: [PATCH 075/141] LibWeb: Stop constructing temporary ElementInlineCSSStyleDeclarations Previously, parse_css_style_attribute() would parse the string, extract the properties, add them to a newly-created ElementInlineCSSStyleDeclarations, and then user code would take the properties back out of it again and throw it away. Instead, just return the list of properties, and the caller can create an EICSD if it needs one. --- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 4 ++-- Libraries/LibWeb/CSS/Parser/Helpers.cpp | 6 +++--- Libraries/LibWeb/CSS/Parser/Parser.cpp | 8 ++++---- Libraries/LibWeb/CSS/Parser/Parser.h | 14 +++++++------- Libraries/LibWeb/DOM/Element.cpp | 9 +++------ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 0f97ffa29f5..4cd7de609cb 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -668,8 +668,8 @@ void ElementInlineCSSStyleDeclaration::set_declarations_from_text(StringView css } empty_the_declarations(); - auto style = parse_css_style_attribute(CSS::Parser::ParsingParams(element->element().document()), css_text, element->element()); - set_the_declarations(style->properties(), style->custom_properties()); + auto style = parse_css_style_attribute(Parser::ParsingParams(element->element().document()), css_text); + set_the_declarations(style.properties, style.custom_properties); } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext diff --git a/Libraries/LibWeb/CSS/Parser/Helpers.cpp b/Libraries/LibWeb/CSS/Parser/Helpers.cpp index c503f65ba7d..8de40db92f0 100644 --- a/Libraries/LibWeb/CSS/Parser/Helpers.cpp +++ b/Libraries/LibWeb/CSS/Parser/Helpers.cpp @@ -30,11 +30,11 @@ CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const& conte return style_sheet; } -CSS::ElementInlineCSSStyleDeclaration* parse_css_style_attribute(CSS::Parser::ParsingParams const& context, StringView css, DOM::Element& element) +CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_style_attribute(CSS::Parser::ParsingParams const& context, StringView css) { if (css.is_empty()) - return CSS::ElementInlineCSSStyleDeclaration::create(element, {}, {}); - return CSS::Parser::Parser::create(context, css).parse_as_style_attribute(element); + return {}; + return CSS::Parser::Parser::create(context, css).parse_as_style_attribute(); } RefPtr parse_css_value(CSS::Parser::ParsingParams const& context, StringView string, CSS::PropertyID property_id) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 441e1de4924..731fbfcdfd9 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -1317,7 +1317,7 @@ Vector> Parser::parse_a_comma_separated_list_of_component return groups; } -ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element& element) +Parser::PropertiesAndCustomProperties Parser::parse_as_style_attribute() { auto expand_shorthands = [&](Vector& properties) -> Vector { Vector expanded_properties; @@ -1341,9 +1341,9 @@ ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element& auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream); m_rule_context.take_last(); - auto [properties, custom_properties] = extract_properties(declarations_and_at_rules); - auto expanded_properties = expand_shorthands(properties); - return ElementInlineCSSStyleDeclaration::create(element, move(expanded_properties), move(custom_properties)); + auto properties = extract_properties(declarations_and_at_rules); + properties.properties = expand_shorthands(properties.properties); + return properties; } bool Parser::is_valid_in_the_current_context(Declaration const&) const diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 99cfa4c59ef..50391d0343f 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -88,7 +88,12 @@ public: static Parser create(ParsingParams const&, StringView input, StringView encoding = "utf-8"sv); CSSStyleSheet* parse_as_css_stylesheet(Optional location); - ElementInlineCSSStyleDeclaration* parse_as_style_attribute(DOM::Element&); + + struct PropertiesAndCustomProperties { + Vector properties; + HashMap custom_properties; + }; + PropertiesAndCustomProperties parse_as_style_attribute(); CSSRule* parse_as_css_rule(); Optional parse_as_supports_condition(); @@ -452,11 +457,6 @@ private: static bool has_ignored_vendor_prefix(StringView); - struct PropertiesAndCustomProperties { - Vector properties; - HashMap custom_properties; - }; - PropertiesAndCustomProperties extract_properties(Vector const&); void extract_property(Declaration const&, Parser::PropertiesAndCustomProperties&); @@ -510,7 +510,7 @@ private: namespace Web { CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const&, StringView, Optional location = {}); -CSS::ElementInlineCSSStyleDeclaration* parse_css_style_attribute(CSS::Parser::ParsingParams const&, StringView, DOM::Element&); +CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_style_attribute(CSS::Parser::ParsingParams const&, StringView); RefPtr parse_css_value(CSS::Parser::ParsingParams const&, StringView, CSS::PropertyID property_id = CSS::PropertyID::Invalid); Optional parse_selector(CSS::Parser::ParsingParams const&, StringView); Optional parse_selector_for_nested_style_rule(CSS::Parser::ParsingParams const&, StringView); diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 36e8224ed22..3479b2a0733 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -3523,12 +3523,9 @@ void Element::attribute_changed(FlyString const& local_name, Optional co // https://drafts.csswg.org/cssom/#ref-for-cssstyledeclaration-updating-flag if (m_inline_style && m_inline_style->is_updating()) return; - if (!m_inline_style) { - m_inline_style = parse_css_style_attribute(CSS::Parser::ParsingParams(document()), *value, *this); - } else { - // NOTE: ElementInlineCSSStyleDeclaration::set_css_text should never throw an exception. - m_inline_style->set_declarations_from_text(*value); - } + if (!m_inline_style) + m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create(*this, {}, {}); + m_inline_style->set_declarations_from_text(*value); set_needs_style_update(true); } } else if (local_name == HTML::AttributeNames::dir) { From 687d32b712f96c49c8b48a668cff4c37e42ebe1c Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 17 Mar 2025 13:04:41 +0000 Subject: [PATCH 076/141] LibWeb: Remove ElementInlineCSSStyleDeclaration entirely All of its behavior has now been moved up into its parent classes. --- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 76 +++++++------------ Libraries/LibWeb/CSS/CSSStyleDeclaration.h | 32 ++------ Libraries/LibWeb/DOM/Element.cpp | 4 +- Libraries/LibWeb/DOM/Element.h | 6 +- Libraries/LibWeb/Forward.h | 1 - .../wpt-import/css/css-nesting/cssom.txt | 5 +- .../nested-declarations-cssom-whitespace.txt | 5 +- 7 files changed, 46 insertions(+), 83 deletions(-) diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 4cd7de609cb..1bec03e8f17 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -23,7 +23,6 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSStyleDeclaration); GC_DEFINE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); -GC_DEFINE_ALLOCATOR(ElementInlineCSSStyleDeclaration); CSSStyleDeclaration::CSSStyleDeclaration(JS::Realm& realm, Computed computed, Readonly readonly) : PlatformObject(realm) @@ -43,14 +42,21 @@ void CSSStyleDeclaration::initialize(JS::Realm& realm) GC::Ref PropertyOwningCSSStyleDeclaration::create(JS::Realm& realm, Vector properties, HashMap custom_properties) { - return realm.create(realm, move(properties), move(custom_properties)); + return realm.create(realm, nullptr, move(properties), move(custom_properties)); } -PropertyOwningCSSStyleDeclaration::PropertyOwningCSSStyleDeclaration(JS::Realm& realm, Vector properties, HashMap custom_properties) +GC::Ref PropertyOwningCSSStyleDeclaration::create_element_inline_style(JS::Realm& realm, GC::Ref element, Vector properties, HashMap custom_properties) +{ + return realm.create(realm, element, move(properties), move(custom_properties)); +} + +PropertyOwningCSSStyleDeclaration::PropertyOwningCSSStyleDeclaration(JS::Realm& realm, GC::Ptr element, Vector properties, HashMap custom_properties) : CSSStyleDeclaration(realm, Computed::No, Readonly::No) , m_properties(move(properties)) , m_custom_properties(move(custom_properties)) { + if (element) + set_owner_node(DOM::ElementReference { *element }); } void PropertyOwningCSSStyleDeclaration::visit_edges(Visitor& visitor) @@ -69,18 +75,6 @@ String PropertyOwningCSSStyleDeclaration::item(size_t index) const return CSS::string_from_property_id(m_properties[index].property_id).to_string(); } -GC::Ref ElementInlineCSSStyleDeclaration::create(DOM::Element& element, Vector properties, HashMap custom_properties) -{ - auto& realm = element.realm(); - return realm.create(element, move(properties), move(custom_properties)); -} - -ElementInlineCSSStyleDeclaration::ElementInlineCSSStyleDeclaration(DOM::Element& element, Vector properties, HashMap custom_properties) - : PropertyOwningCSSStyleDeclaration(element.realm(), move(properties), move(custom_properties)) -{ - set_owner_node(DOM::ElementReference { element }); -} - size_t PropertyOwningCSSStyleDeclaration::length() const { return m_properties.size(); @@ -210,7 +204,7 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(S } // https://drafts.csswg.org/cssom/#update-style-attribute-for -void ElementInlineCSSStyleDeclaration::update_style_attribute() +void CSSStyleDeclaration::update_style_attribute() { // 1. Assert: declaration block’s computed flag is unset. VERIFY(!is_computed()); @@ -640,9 +634,21 @@ String PropertyOwningCSSStyleDeclaration::serialized() const return MUST(builder.to_string()); } +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_css_text(StringView css_text) { - dbgln("(STUBBED) PropertyOwningCSSStyleDeclaration::set_css_text(css_text='{}')", css_text); + // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. + if (is_readonly()) { + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties: CSSStyleDeclaration is read-only."_string); + } + + // 2. Empty the declarations. + // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the declarations, in specified order. + set_declarations_from_text(css_text); + + // 4. Update style attribute for the CSS declaration block. + update_style_attribute(); + return {}; } @@ -658,40 +664,14 @@ void PropertyOwningCSSStyleDeclaration::set_the_declarations(Vectorelement().document()), css_text); + auto parsing_params = owner_node().has_value() + ? Parser::ParsingParams(owner_node()->element().document()) + : Parser::ParsingParams(); + auto style = parse_css_style_attribute(parsing_params, css_text); set_the_declarations(style.properties, style.custom_properties); } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext -WebIDL::ExceptionOr ElementInlineCSSStyleDeclaration::set_css_text(StringView css_text) -{ - // FIXME: What do we do if the element is null? - if (!owner_node().has_value()) { - dbgln("FIXME: Returning from ElementInlineCSSStyleDeclaration::set_css_text as element is null."); - return {}; - } - - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - // NOTE: See ResolvedCSSStyleDeclaration. - - // 2. Empty the declarations. - // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the declarations, in specified order. - set_declarations_from_text(css_text); - - // 4. Update style attribute for the CSS declaration block. - update_style_attribute(); - - return {}; -} - } diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index 08b78acdd71..0092fa13cc6 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -84,6 +84,8 @@ protected: virtual CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() override { return *this; } + void update_style_attribute(); + private: // ^PlatformObject virtual Optional item_value(size_t index) const override; @@ -109,12 +111,13 @@ class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration { WEB_PLATFORM_OBJECT(PropertyOwningCSSStyleDeclaration, CSSStyleDeclaration); GC_DECLARE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); - friend class ElementInlineCSSStyleDeclaration; - public: [[nodiscard]] static GC::Ref create(JS::Realm&, Vector, HashMap custom_properties); + [[nodiscard]] static GC::Ref + create_element_inline_style(JS::Realm&, GC::Ref, Vector, HashMap custom_properties); + virtual ~PropertyOwningCSSStyleDeclaration() override = default; virtual size_t length() const override; @@ -133,10 +136,10 @@ public: virtual String serialized() const final override; virtual WebIDL::ExceptionOr set_css_text(StringView) override; -protected: - PropertyOwningCSSStyleDeclaration(JS::Realm&, Vector, HashMap); + void set_declarations_from_text(StringView); - virtual void update_style_attribute() { } +protected: + PropertyOwningCSSStyleDeclaration(JS::Realm&, GC::Ptr owner_node, Vector, HashMap); void empty_the_declarations(); void set_the_declarations(Vector properties, HashMap custom_properties); @@ -150,23 +153,4 @@ private: HashMap m_custom_properties; }; -class ElementInlineCSSStyleDeclaration final : public PropertyOwningCSSStyleDeclaration { - WEB_PLATFORM_OBJECT(ElementInlineCSSStyleDeclaration, PropertyOwningCSSStyleDeclaration); - GC_DECLARE_ALLOCATOR(ElementInlineCSSStyleDeclaration); - -public: - [[nodiscard]] static GC::Ref create(DOM::Element&, Vector, HashMap custom_properties); - - virtual ~ElementInlineCSSStyleDeclaration() override = default; - - void set_declarations_from_text(StringView); - - virtual WebIDL::ExceptionOr set_css_text(StringView) override; - -private: - ElementInlineCSSStyleDeclaration(DOM::Element&, Vector properties, HashMap custom_properties); - - virtual void update_style_attribute() override; -}; - } diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 3479b2a0733..10f137cd7f8 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -921,7 +921,7 @@ void Element::set_shadow_root(GC::Ptr shadow_root) CSS::CSSStyleDeclaration* Element::style_for_bindings() { if (!m_inline_style) - m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create(*this, {}, {}); + m_inline_style = CSS::PropertyOwningCSSStyleDeclaration::create_element_inline_style(realm(), *this, {}, {}); return m_inline_style; } @@ -3524,7 +3524,7 @@ void Element::attribute_changed(FlyString const& local_name, Optional co if (m_inline_style && m_inline_style->is_updating()) return; if (!m_inline_style) - m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create(*this, {}, {}); + m_inline_style = CSS::PropertyOwningCSSStyleDeclaration::create_element_inline_style(realm(), *this, {}, {}); m_inline_style->set_declarations_from_text(*value); set_needs_style_update(true); } diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index 29bc2052a74..d2fba9fe53e 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -211,8 +211,8 @@ public: void reset_animated_css_properties(); - GC::Ptr inline_style() { return m_inline_style; } - GC::Ptr inline_style() const { return m_inline_style; } + GC::Ptr inline_style() { return m_inline_style; } + GC::Ptr inline_style() const { return m_inline_style; } CSS::CSSStyleDeclaration* style_for_bindings(); @@ -499,7 +499,7 @@ private: FlyString m_html_uppercased_qualified_name; GC::Ptr m_attributes; - GC::Ptr m_inline_style; + GC::Ptr m_inline_style; GC::Ptr m_class_list; GC::Ptr m_shadow_root; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index a0b41e4c5af..b3f539a1089 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -189,7 +189,6 @@ class Display; class DisplayStyleValue; class EasingStyleValue; class EdgeStyleValue; -class ElementInlineCSSStyleDeclaration; class ExplicitGridTrack; class FilterValueListStyleValue; class FitContentStyleValue; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/cssom.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/cssom.txt index 75f7d9a0f11..8c2bfc74d14 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/cssom.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/cssom.txt @@ -2,8 +2,7 @@ Harness status: OK Found 13 tests -12 Pass -1 Fail +13 Pass Pass CSSStyleRule is a CSSGroupingRule Pass Simple CSSOM manipulation of subrules Pass Simple CSSOM manipulation of subrules 1 @@ -13,7 +12,7 @@ Pass Simple CSSOM manipulation of subrules 4 Pass Simple CSSOM manipulation of subrules 5 Pass Simple CSSOM manipulation of subrules 6 Pass Simple CSSOM manipulation of subrules 7 -Fail Simple CSSOM manipulation of subrules 8 +Pass Simple CSSOM manipulation of subrules 8 Pass Simple CSSOM manipulation of subrules 9 Pass Simple CSSOM manipulation of subrules 10 Pass Mutating the selectorText of outer rule invalidates inner rules \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/nested-declarations-cssom-whitespace.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/nested-declarations-cssom-whitespace.txt index 828018cd051..2d94c4eb749 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/nested-declarations-cssom-whitespace.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-nesting/nested-declarations-cssom-whitespace.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail -Fail Empty CSSNestedDeclarations do not affect outer serialization +1 Pass +1 Fail +Pass Empty CSSNestedDeclarations do not affect outer serialization Fail Empty CSSNestedDeclarations do not affect outer serialization (nested grouping rule) \ No newline at end of file From 83bb92c4e0fa230335cedc27afeb3b19e92ee6e3 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 17 Mar 2025 17:50:49 +0000 Subject: [PATCH 077/141] LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties We previously had PropertyOwningCSSStyleDeclaration and ResolvedCSSStyleDeclaration, representing the current style properties and resolved style respectively. Both of these were the CSSStyleDeclaration type in the CSSOM. (We also had ElementInlineCSSStyleDeclaration but I removed that in a previous commit.) In the meantime, the spec has changed so that these should now be a new CSSStyleProperties type in the CSSOM. Also, we need to subclass CSSStyleDeclaration for things like CSSFontFaceRule's list of descriptors, which means it wouldn't hold style properties. So, this commit does the fairly messy work of combining these two types into a new CSSStyleProperties class. A lot of what previously was done as separate methods in the two classes, now follows the spec steps of "if the readonly flag is set, do X" instead, which is hopefully easier to follow too. There is still some functionality in CSSStyleDeclaration that belongs in CSSStyleProperties, but I'll do that next. To avoid a huge diff for "CSSStyleDeclaration-all-supported-properties-and-default-values.txt" both here and in the following commit, we don't apply the (currently empty) CSSStyleProperties prototype yet. --- Documentation/CSSProperties.md | 2 +- Libraries/LibWeb/CMakeLists.txt | 2 +- Libraries/LibWeb/CSS/CSSKeyframeRule.cpp | 4 +- Libraries/LibWeb/CSS/CSSKeyframeRule.h | 11 +- Libraries/LibWeb/CSS/CSSKeyframeRule.idl | 4 +- .../LibWeb/CSS/CSSNestedDeclarations.cpp | 4 +- Libraries/LibWeb/CSS/CSSNestedDeclarations.h | 11 +- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 353 +---------- Libraries/LibWeb/CSS/CSSStyleDeclaration.h | 46 -- ...Declaration.cpp => CSSStyleProperties.cpp} | 580 ++++++++++++++---- Libraries/LibWeb/CSS/CSSStyleProperties.h | 64 ++ Libraries/LibWeb/CSS/CSSStyleProperties.idl | 6 + Libraries/LibWeb/CSS/CSSStyleRule.cpp | 6 +- Libraries/LibWeb/CSS/CSSStyleRule.h | 12 +- Libraries/LibWeb/CSS/CSSStyleRule.idl | 4 +- .../LibWeb/CSS/ElementCSSInlineStyle.idl | 4 +- Libraries/LibWeb/CSS/Parser/Parser.cpp | 5 +- Libraries/LibWeb/CSS/Parser/Parser.h | 2 +- Libraries/LibWeb/CSS/Parser/RuleParsing.cpp | 5 +- .../LibWeb/CSS/ResolvedCSSStyleDeclaration.h | 42 -- Libraries/LibWeb/CSS/StyleComputer.cpp | 7 +- Libraries/LibWeb/CSS/StyleComputer.h | 2 +- Libraries/LibWeb/DOM/Element.cpp | 10 +- Libraries/LibWeb/DOM/Element.h | 8 +- Libraries/LibWeb/Dump.cpp | 9 +- Libraries/LibWeb/Dump.h | 2 +- .../LibWeb/Editing/Internal/Algorithms.cpp | 3 +- Libraries/LibWeb/Forward.h | 2 +- Libraries/LibWeb/HTML/Window.cpp | 14 +- Libraries/LibWeb/idl_files.cmake | 1 + .../Userland/Libraries/LibWeb/CSS/BUILD.gn | 2 +- .../Text/expected/all-window-properties.txt | 1 + 32 files changed, 589 insertions(+), 639 deletions(-) rename Libraries/LibWeb/CSS/{ResolvedCSSStyleDeclaration.cpp => CSSStyleProperties.cpp} (58%) create mode 100644 Libraries/LibWeb/CSS/CSSStyleProperties.h create mode 100644 Libraries/LibWeb/CSS/CSSStyleProperties.idl delete mode 100644 Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h diff --git a/Documentation/CSSProperties.md b/Documentation/CSSProperties.md index ad60b2603fa..d2c26def7c3 100644 --- a/Documentation/CSSProperties.md +++ b/Documentation/CSSProperties.md @@ -59,5 +59,5 @@ bool Paintable::is_visible() const ## JavaScript Some properties have special rules for getting the computed value from JS. For these, you will need to add to -`ResolvedCSSStyleDeclaration::style_value_for_property()`. Shorthands that are constructed in an unusual way (as in, not +`CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are constructed in an unusual way (as in, not using `ShorthandStyleValue`) also need handling inside `CSSStyleDeclaration::get_property_internal()`. diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 4e0a70eac0f..75b29cce574 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -81,6 +81,7 @@ set(SOURCES CSS/CSSRule.cpp CSS/CSSRuleList.cpp CSS/CSSStyleDeclaration.cpp + CSS/CSSStyleProperties.cpp CSS/CSSStyleRule.cpp CSS/CSSStyleSheet.cpp CSS/CSSStyleValue.cpp @@ -124,7 +125,6 @@ set(SOURCES CSS/PreferredMotion.cpp CSS/Ratio.cpp CSS/Resolution.cpp - CSS/ResolvedCSSStyleDeclaration.cpp CSS/Screen.cpp CSS/ScreenOrientation.cpp CSS/Selector.cpp diff --git a/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp b/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp index ede8de3ce00..f057bf38405 100644 --- a/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp +++ b/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp @@ -13,12 +13,12 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSKeyframeRule); -GC::Ref CSSKeyframeRule::create(JS::Realm& realm, CSS::Percentage key, Web::CSS::PropertyOwningCSSStyleDeclaration& declarations) +GC::Ref CSSKeyframeRule::create(JS::Realm& realm, Percentage key, CSSStyleProperties& declarations) { return realm.create(realm, key, declarations); } -CSSKeyframeRule::CSSKeyframeRule(JS::Realm& realm, CSS::Percentage key, PropertyOwningCSSStyleDeclaration& declarations) +CSSKeyframeRule::CSSKeyframeRule(JS::Realm& realm, Percentage key, CSSStyleProperties& declarations) : CSSRule(realm, Type::Keyframe) , m_key(key) , m_declarations(declarations) diff --git a/Libraries/LibWeb/CSS/CSSKeyframeRule.h b/Libraries/LibWeb/CSS/CSSKeyframeRule.h index 343066bda35..c6be31e54ad 100644 --- a/Libraries/LibWeb/CSS/CSSKeyframeRule.h +++ b/Libraries/LibWeb/CSS/CSSKeyframeRule.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -21,13 +21,12 @@ class CSSKeyframeRule final : public CSSRule { GC_DECLARE_ALLOCATOR(CSSKeyframeRule); public: - static GC::Ref create(JS::Realm&, CSS::Percentage key, PropertyOwningCSSStyleDeclaration&); + static GC::Ref create(JS::Realm&, CSS::Percentage key, CSSStyleProperties&); virtual ~CSSKeyframeRule() = default; CSS::Percentage key() const { return m_key; } - GC::Ref style() const { return m_declarations; } - GC::Ref style_as_property_owning_style_declaration() const { return m_declarations; } + GC::Ref style() const { return m_declarations; } String key_text() const { @@ -40,14 +39,14 @@ public: } private: - CSSKeyframeRule(JS::Realm&, CSS::Percentage, PropertyOwningCSSStyleDeclaration&); + CSSKeyframeRule(JS::Realm&, CSS::Percentage, CSSStyleProperties&); virtual void visit_edges(Visitor&) override; virtual void initialize(JS::Realm&) override; virtual String serialized() const override; CSS::Percentage m_key; - GC::Ref m_declarations; + GC::Ref m_declarations; }; template<> diff --git a/Libraries/LibWeb/CSS/CSSKeyframeRule.idl b/Libraries/LibWeb/CSS/CSSKeyframeRule.idl index 1d6f21767ec..9daf1560e16 100644 --- a/Libraries/LibWeb/CSS/CSSKeyframeRule.idl +++ b/Libraries/LibWeb/CSS/CSSKeyframeRule.idl @@ -1,9 +1,9 @@ #import -#import +#import // https://drafts.csswg.org/css-animations-1/#interface-csskeyframerule-idl [Exposed=Window] interface CSSKeyframeRule : CSSRule { attribute CSSOMString keyText; - [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; + [SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style; }; diff --git a/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp b/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp index 0463f865422..4bed40058d7 100644 --- a/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp +++ b/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp @@ -13,12 +13,12 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSNestedDeclarations); -GC::Ref CSSNestedDeclarations::create(JS::Realm& realm, PropertyOwningCSSStyleDeclaration& declaration) +GC::Ref CSSNestedDeclarations::create(JS::Realm& realm, CSSStyleProperties& declaration) { return realm.create(realm, declaration); } -CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, PropertyOwningCSSStyleDeclaration& declaration) +CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, CSSStyleProperties& declaration) : CSSRule(realm, Type::NestedDeclarations) , m_declaration(declaration) { diff --git a/Libraries/LibWeb/CSS/CSSNestedDeclarations.h b/Libraries/LibWeb/CSS/CSSNestedDeclarations.h index 18206b52664..34b6db14273 100644 --- a/Libraries/LibWeb/CSS/CSSNestedDeclarations.h +++ b/Libraries/LibWeb/CSS/CSSNestedDeclarations.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Sam Atkins + * Copyright (c) 2024-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +7,7 @@ #pragma once #include +#include namespace Web::CSS { @@ -15,25 +16,25 @@ class CSSNestedDeclarations final : public CSSRule { GC_DECLARE_ALLOCATOR(CSSNestedDeclarations); public: - [[nodiscard]] static GC::Ref create(JS::Realm&, PropertyOwningCSSStyleDeclaration&); + [[nodiscard]] static GC::Ref create(JS::Realm&, CSSStyleProperties&); virtual ~CSSNestedDeclarations() override = default; - PropertyOwningCSSStyleDeclaration const& declaration() const { return m_declaration; } + CSSStyleProperties const& declaration() const { return m_declaration; } CSSStyleDeclaration* style(); CSSStyleRule const& parent_style_rule() const; private: - CSSNestedDeclarations(JS::Realm&, PropertyOwningCSSStyleDeclaration&); + CSSNestedDeclarations(JS::Realm&, CSSStyleProperties&); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; virtual String serialized() const override; virtual void clear_caches() override; - GC::Ref m_declaration; + GC::Ref m_declaration; GC::Ptr mutable m_parent_style_rule; }; diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 1bec03e8f17..4860098c687 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -17,12 +17,10 @@ #include #include #include -#include namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSStyleDeclaration); -GC_DEFINE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); CSSStyleDeclaration::CSSStyleDeclaration(JS::Realm& realm, Computed computed, Readonly readonly) : PlatformObject(realm) @@ -40,169 +38,6 @@ void CSSStyleDeclaration::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleDeclaration); } -GC::Ref PropertyOwningCSSStyleDeclaration::create(JS::Realm& realm, Vector properties, HashMap custom_properties) -{ - return realm.create(realm, nullptr, move(properties), move(custom_properties)); -} - -GC::Ref PropertyOwningCSSStyleDeclaration::create_element_inline_style(JS::Realm& realm, GC::Ref element, Vector properties, HashMap custom_properties) -{ - return realm.create(realm, element, move(properties), move(custom_properties)); -} - -PropertyOwningCSSStyleDeclaration::PropertyOwningCSSStyleDeclaration(JS::Realm& realm, GC::Ptr element, Vector properties, HashMap custom_properties) - : CSSStyleDeclaration(realm, Computed::No, Readonly::No) - , m_properties(move(properties)) - , m_custom_properties(move(custom_properties)) -{ - if (element) - set_owner_node(DOM::ElementReference { *element }); -} - -void PropertyOwningCSSStyleDeclaration::visit_edges(Visitor& visitor) -{ - Base::visit_edges(visitor); - for (auto& property : m_properties) { - if (property.value->is_image()) - property.value->as_image().visit_edges(visitor); - } -} - -String PropertyOwningCSSStyleDeclaration::item(size_t index) const -{ - if (index >= m_properties.size()) - return {}; - return CSS::string_from_property_id(m_properties[index].property_id).to_string(); -} - -size_t PropertyOwningCSSStyleDeclaration::length() const -{ - return m_properties.size(); -} - -Optional PropertyOwningCSSStyleDeclaration::property(PropertyID property_id) const -{ - for (auto& property : m_properties) { - if (property.property_id == property_id) - return property; - } - return {}; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(StringView property_name, StringView value, StringView priority) -{ - auto maybe_property_id = property_id_from_string(property_name); - if (!maybe_property_id.has_value()) - return {}; - auto property_id = maybe_property_id.value(); - - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - // NOTE: This is handled by the virtual override in ResolvedCSSStyleDeclaration. - - // FIXME: 2. If property is not a custom property, follow these substeps: - // FIXME: 1. Let property be property converted to ASCII lowercase. - // FIXME: 2. If property is not a case-sensitive match for a supported CSS property, then return. - // NOTE: This must be handled before we've turned the property string into a PropertyID. - - // 3. If value is the empty string, invoke removeProperty() with property as argument and return. - if (value.is_empty()) { - MUST(remove_property(property_name)); - return {}; - } - - // 4. If priority is not the empty string and is not an ASCII case-insensitive match for the string "important", then return. - if (!priority.is_empty() && !Infra::is_ascii_case_insensitive_match(priority, "important"sv)) - return {}; - - // 5. Let component value list be the result of parsing value for property property. - auto component_value_list = owner_node().has_value() - ? parse_css_value(CSS::Parser::ParsingParams { owner_node()->element().document() }, value, property_id) - : parse_css_value(CSS::Parser::ParsingParams {}, value, property_id); - - // 6. If component value list is null, then return. - if (!component_value_list) - return {}; - - // 7. Let updated be false. - bool updated = false; - - // 8. If property is a shorthand property, - if (property_is_shorthand(property_id)) { - // then for each longhand property longhand that property maps to, in canonical order, follow these substeps: - StyleComputer::for_each_property_expanding_shorthands(property_id, *component_value_list, StyleComputer::AllowUnresolved::Yes, [this, &updated, priority](PropertyID longhand_property_id, CSSStyleValue const& longhand_value) { - // 1. Let longhand result be the result of set the CSS declaration longhand with the appropriate value(s) from component value list, - // with the important flag set if priority is not the empty string, and unset otherwise, and with the list of declarations being the declarations. - // 2. If longhand result is true, let updated be true. - updated |= set_a_css_declaration(longhand_property_id, longhand_value, !priority.is_empty() ? Important::Yes : Important::No); - }); - } - // 9. Otherwise, - else { - if (property_id == PropertyID::Custom) { - auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); - StyleProperty style_property { - .important = !priority.is_empty() ? Important::Yes : Important::No, - .property_id = property_id, - .value = component_value_list.release_nonnull(), - .custom_name = custom_name, - }; - m_custom_properties.set(custom_name, style_property); - updated = true; - } else { - // let updated be the result of set the CSS declaration property with value component value list, - // with the important flag set if priority is not the empty string, and unset otherwise, - // and with the list of declarations being the declarations. - updated = set_a_css_declaration(property_id, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No); - } - } - - // 10. If updated is true, update style attribute for the CSS declaration block. - if (updated) - update_style_attribute(); - - return {}; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(StringView property_name) -{ - auto property_id = property_id_from_string(property_name); - if (!property_id.has_value()) - return String {}; - - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - // NOTE: This is handled by the virtual override in ResolvedCSSStyleDeclaration. - - // 2. If property is not a custom property, let property be property converted to ASCII lowercase. - // NOTE: We've already converted it to a PropertyID enum value. - - // 3. Let value be the return value of invoking getPropertyValue() with property as argument. - auto value = get_property_value(property_name); - - // 4. Let removed be false. - bool removed = false; - - // FIXME: 5. If property is a shorthand property, for each longhand property longhand that property maps to: - // 1. If longhand is not a property name of a CSS declaration in the declarations, continue. - // 2. Remove that CSS declaration and let removed be true. - - // 6. Otherwise, if property is a case-sensitive match for a property name of a CSS declaration in the declarations, remove that CSS declaration and let removed be true. - if (property_id == PropertyID::Custom) { - auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); - removed = m_custom_properties.remove(custom_name); - } else { - removed = m_properties.remove_first_matching([&](auto& entry) { return entry.property_id == property_id; }); - } - - // 7. If removed is true, Update style attribute for the CSS declaration block. - if (removed) - update_style_attribute(); - - // 8. Return value. - return value; -} - // https://drafts.csswg.org/cssom/#update-style-attribute-for void CSSStyleDeclaration::update_style_attribute() { @@ -224,29 +59,6 @@ void CSSStyleDeclaration::update_style_attribute() set_is_updating(false); } -// https://drafts.csswg.org/cssom/#set-a-css-declaration -bool PropertyOwningCSSStyleDeclaration::set_a_css_declaration(PropertyID property_id, NonnullRefPtr value, Important important) -{ - // FIXME: Handle logical property groups. - - for (auto& property : m_properties) { - if (property.property_id == property_id) { - if (property.important == important && *property.value == *value) - return false; - property.value = move(value); - property.important = important; - return true; - } - } - - m_properties.append(CSS::StyleProperty { - .important = important, - .property_id = property_id, - .value = move(value), - }); - return true; -} - static Optional style_property_for_sided_shorthand(PropertyID property_id, Optional const& top, Optional const& right, Optional const& bottom, Optional const& left) { if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value()) @@ -475,7 +287,8 @@ WebIDL::ExceptionOr CSSStyleDeclaration::remove_property(PropertyID prop String CSSStyleDeclaration::css_text() const { // 1. If the computed flag is set, then return the empty string. - // NOTE: See ResolvedCSSStyleDeclaration::serialized() + if (is_computed()) + return {}; // 2. Return the result of serializing the declarations. return serialized(); @@ -512,166 +325,4 @@ void CSSStyleDeclaration::visit_edges(Visitor& visitor) m_owner_node->visit(visitor); } -// https://www.w3.org/TR/cssom/#serialize-a-css-declaration -static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) -{ - StringBuilder builder; - - // 1. Let s be the empty string. - // 2. Append property to s. - builder.append(string_from_property_id(property)); - - // 3. Append ": " (U+003A U+0020) to s. - builder.append(": "sv); - - // 4. Append value to s. - builder.append(value); - - // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. - if (important == Important::Yes) - builder.append(" !important"sv); - - // 6. Append ";" (U+003B) to s. - builder.append(';'); - - // 7. Return s. - return MUST(builder.to_string()); -} - -// https://www.w3.org/TR/cssom/#serialize-a-css-declaration-block -String PropertyOwningCSSStyleDeclaration::serialized() const -{ - // 1. Let list be an empty array. - Vector list; - - // 2. Let already serialized be an empty array. - HashTable already_serialized; - - // NOTE: The spec treats custom properties the same as any other property, and expects the above loop to handle them. - // However, our implementation separates them from regular properties, so we need to handle them separately here. - // FIXME: Is the relative order of custom properties and regular properties supposed to be preserved? - for (auto& declaration : m_custom_properties) { - // 1. Let property be declaration’s property name. - auto const& property = declaration.key; - - // 2. If property is in already serialized, continue with the steps labeled declaration loop. - // NOTE: It is never in already serialized, as there are no shorthands for custom properties. - - // 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. - // NOTE: There are no shorthands for custom properties. - - // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... - // NOTE: There are no shorthands for custom properties. - - // 5. Let value be the result of invoking serialize a CSS value of declaration. - auto value = declaration.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); - - // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, - // and the important flag set if declaration has its important flag set. - // NOTE: We have to inline this here as the actual implementation does not accept custom properties. - String serialized_declaration = [&] { - // https://www.w3.org/TR/cssom/#serialize-a-css-declaration - StringBuilder builder; - - // 1. Let s be the empty string. - // 2. Append property to s. - builder.append(property); - - // 3. Append ": " (U+003A U+0020) to s. - builder.append(": "sv); - - // 4. Append value to s. - builder.append(value); - - // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. - if (declaration.value.important == Important::Yes) - builder.append(" !important"sv); - - // 6. Append ";" (U+003B) to s. - builder.append(';'); - - // 7. Return s. - return MUST(builder.to_string()); - }(); - - // 7. Append serialized declaration to list. - list.append(move(serialized_declaration)); - - // 8. Append property to already serialized. - // NOTE: We don't need to do this, as we don't have shorthands for custom properties. - } - - // 3. Declaration loop: For each CSS declaration declaration in declaration block’s declarations, follow these substeps: - for (auto& declaration : m_properties) { - // 1. Let property be declaration’s property name. - auto property = declaration.property_id; - - // 2. If property is in already serialized, continue with the steps labeled declaration loop. - if (already_serialized.contains(property)) - continue; - - // FIXME: 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. - - // FIXME: 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... - - // 5. Let value be the result of invoking serialize a CSS value of declaration. - auto value = declaration.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); - - // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, - // and the important flag set if declaration has its important flag set. - auto serialized_declaration = serialize_a_css_declaration(property, move(value), declaration.important); - - // 7. Append serialized declaration to list. - list.append(move(serialized_declaration)); - - // 8. Append property to already serialized. - already_serialized.set(property); - } - - // 4. Return list joined with " " (U+0020). - StringBuilder builder; - builder.join(' ', list); - return MUST(builder.to_string()); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_css_text(StringView css_text) -{ - // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. - if (is_readonly()) { - return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties: CSSStyleDeclaration is read-only."_string); - } - - // 2. Empty the declarations. - // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the declarations, in specified order. - set_declarations_from_text(css_text); - - // 4. Update style attribute for the CSS declaration block. - update_style_attribute(); - - return {}; -} - -void PropertyOwningCSSStyleDeclaration::empty_the_declarations() -{ - m_properties.clear(); - m_custom_properties.clear(); -} - -void PropertyOwningCSSStyleDeclaration::set_the_declarations(Vector properties, HashMap custom_properties) -{ - m_properties = move(properties); - m_custom_properties = move(custom_properties); -} - -void PropertyOwningCSSStyleDeclaration::set_declarations_from_text(StringView css_text) -{ - empty_the_declarations(); - auto parsing_params = owner_node().has_value() - ? Parser::ParsingParams(owner_node()->element().document()) - : Parser::ParsingParams(); - auto style = parse_css_style_attribute(parsing_params, css_text); - set_the_declarations(style.properties, style.custom_properties); -} - } diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index 0092fa13cc6..5e039e26107 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -107,50 +107,4 @@ private: bool m_updating { false }; }; -class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration { - WEB_PLATFORM_OBJECT(PropertyOwningCSSStyleDeclaration, CSSStyleDeclaration); - GC_DECLARE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); - -public: - [[nodiscard]] static GC::Ref - create(JS::Realm&, Vector, HashMap custom_properties); - - [[nodiscard]] static GC::Ref - create_element_inline_style(JS::Realm&, GC::Ref, Vector, HashMap custom_properties); - - virtual ~PropertyOwningCSSStyleDeclaration() override = default; - - virtual size_t length() const override; - virtual String item(size_t index) const override; - - virtual Optional property(PropertyID) const override; - virtual Optional custom_property(FlyString const& custom_property_name) const override { return m_custom_properties.get(custom_property_name); } - - virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; - Vector const& properties() const { return m_properties; } - HashMap const& custom_properties() const { return m_custom_properties; } - - size_t custom_property_count() const { return m_custom_properties.size(); } - - virtual String serialized() const final override; - virtual WebIDL::ExceptionOr set_css_text(StringView) override; - - void set_declarations_from_text(StringView); - -protected: - PropertyOwningCSSStyleDeclaration(JS::Realm&, GC::Ptr owner_node, Vector, HashMap); - - void empty_the_declarations(); - void set_the_declarations(Vector properties, HashMap custom_properties); - -private: - bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); - - virtual void visit_edges(Cell::Visitor&) override; - - Vector m_properties; - HashMap m_custom_properties; -}; - } diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp similarity index 58% rename from Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp rename to Libraries/LibWeb/CSS/CSSStyleProperties.cpp index cba2e8bf139..aadb05a82d3 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -1,66 +1,281 @@ /* - * Copyright (c) 2021-2025, Andreas Kling - * Copyright (c) 2021, Tobias Christiansen - * Copyright (c) 2022-2025, Sam Atkins + * Copyright (c) 2018-2024, Andreas Kling + * Copyright (c) 2023-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include +#include +#include +#include +#include #include -#include +#include #include -#include -#include -#include #include #include +#include #include #include #include -#include #include #include #include #include #include +#include +#include +#include #include -#include namespace Web::CSS { -GC_DEFINE_ALLOCATOR(ResolvedCSSStyleDeclaration); +GC_DEFINE_ALLOCATOR(CSSStyleProperties); -GC::Ref ResolvedCSSStyleDeclaration::create(DOM::Element& element, Optional pseudo_element) +GC::Ref CSSStyleProperties::create(JS::Realm& realm, Vector properties, HashMap custom_properties) { - return element.realm().create(element, move(pseudo_element)); + // https://drafts.csswg.org/cssom/#dom-cssstylerule-style + // The style attribute must return a CSSStyleProperties object for the style rule, with the following properties: + // computed flag: Unset. + // readonly flag: Unset. + // declarations: The declared declarations in the rule, in specified order. + // parent CSS rule: The context object. + // owner node: Null. + return realm.create(realm, Computed::No, Readonly::No, move(properties), move(custom_properties), OptionalNone {}); } -ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element, Optional pseudo_element) - : CSSStyleDeclaration(element.realm(), Computed::Yes, Readonly::Yes) +GC::Ref CSSStyleProperties::create_resolved_style(DOM::ElementReference element_reference) { - set_owner_node(DOM::ElementReference { element, pseudo_element }); + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle + // 6. Return a live CSSStyleProperties object with the following properties: + // computed flag: Set. + // readonly flag: Set. + // declarations: decls. + // parent CSS rule: Null. + // owner node: obj. + // AD-HOC: Rather than instantiate with a list of decls, they're generated on demand. + auto& realm = element_reference.element().realm(); + return realm.create(realm, Computed::Yes, Readonly::Yes, Vector {}, HashMap {}, move(element_reference)); +} + +GC::Ref CSSStyleProperties::create_element_inline_style(DOM::ElementReference element_reference, Vector properties, HashMap custom_properties) +{ + // https://drafts.csswg.org/cssom/#dom-elementcssinlinestyle-style + // The style attribute must return a CSS declaration block object whose readonly flag is unset, whose parent CSS + // rule is null, and whose owner node is the context object. + auto& realm = element_reference.element().realm(); + return realm.create(realm, Computed::No, Readonly::No, move(properties), move(custom_properties), move(element_reference)); +} + +CSSStyleProperties::CSSStyleProperties(JS::Realm& realm, Computed computed, Readonly readonly, Vector properties, HashMap custom_properties, Optional owner_node) + : CSSStyleDeclaration(realm, computed, readonly) + , m_properties(move(properties)) + , m_custom_properties(move(custom_properties)) +{ + set_owner_node(move(owner_node)); +} + +void CSSStyleProperties::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + // Temporarily disabled for a single commit to make the changes cleaner. + // WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties); +} + +void CSSStyleProperties::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + for (auto& property : m_properties) { + if (property.value->is_image()) + property.value->as_image().visit_edges(visitor); + } } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length -size_t ResolvedCSSStyleDeclaration::length() const +size_t CSSStyleProperties::length() const { // The length attribute must return the number of CSS declarations in the declarations. // FIXME: Include the number of custom properties. - return to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1; + + if (is_computed()) + return to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1; + + return m_properties.size(); } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item -String ResolvedCSSStyleDeclaration::item(size_t index) const +String CSSStyleProperties::item(size_t index) const { // The item(index) method must return the property name of the CSS declaration at position index. - // FIXME: Return custom properties if index > last_longhand_property_id. + // FIXME: Include custom properties. + if (index >= length()) return {}; - auto property_id = static_cast(index + to_underlying(first_longhand_property_id)); - return string_from_property_id(property_id).to_string(); + + if (is_computed()) { + auto property_id = static_cast(index + to_underlying(first_longhand_property_id)); + return string_from_property_id(property_id).to_string(); + } + + return CSS::string_from_property_id(m_properties[index].property_id).to_string(); +} + +Optional CSSStyleProperties::property(PropertyID property_id) const +{ + if (is_computed()) { + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); + + // https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle + // NB: This is a partial enforcement of step 5 ("If elt is connected, ...") + if (!element.is_connected()) + return {}; + + auto get_layout_node = [&]() { + if (pseudo_element.has_value()) + return element.get_pseudo_element_node(pseudo_element.value()); + return element.layout_node(); + }; + + Layout::NodeWithStyle* layout_node = get_layout_node(); + + // FIXME: Be smarter about updating layout if there's no layout node. + // We may legitimately have no layout node if we're not visible, but this protects against situations + // where we're requesting the computed style before layout has happened. + if (!layout_node || property_affects_layout(property_id)) { + element.document().update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty); + layout_node = get_layout_node(); + } else { + // FIXME: If we had a way to update style for a single element, this would be a good place to use it. + element.document().update_style(); + } + + if (!layout_node) { + auto style = element.document().style_computer().compute_style(element, pseudo_element); + + // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. + auto const* value = style->maybe_null_property(property_id); + if (!value) { + dbgln("FIXME: CSSStyleProperties::property(property_id={:#x}) No value for property ID in newly computed style case.", to_underlying(property_id)); + return {}; + } + return StyleProperty { + .property_id = property_id, + .value = *value, + }; + } + + auto value = style_value_for_computed_property(*layout_node, property_id); + if (!value) + return {}; + return StyleProperty { + .property_id = property_id, + .value = *value, + }; + } + + for (auto& property : m_properties) { + if (property.property_id == property_id) + return property; + } + return {}; +} + +Optional CSSStyleProperties::custom_property(FlyString const& custom_property_name) const +{ + if (is_computed()) { + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); + + element.document().update_style(); + + auto const* element_to_check = &element; + while (element_to_check) { + if (auto property = element_to_check->custom_properties(pseudo_element).get(custom_property_name); property.has_value()) + return *property; + + element_to_check = element_to_check->parent_element(); + } + + return {}; + } + + return m_custom_properties.get(custom_property_name); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty +WebIDL::ExceptionOr CSSStyleProperties::set_property(StringView property_name, StringView value, StringView priority) +{ + // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. + if (is_computed()) + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()"_string); + + // FIXME: 2. If property is not a custom property, follow these substeps: + // FIXME: 1. Let property be property converted to ASCII lowercase. + // FIXME: 2. If property is not a case-sensitive match for a supported CSS property, then return. + // NB: This must be handled before we've turned the property string into a PropertyID. + + auto maybe_property_id = property_id_from_string(property_name); + if (!maybe_property_id.has_value()) + return {}; + auto property_id = maybe_property_id.value(); + + // 3. If value is the empty string, invoke removeProperty() with property as argument and return. + if (value.is_empty()) { + MUST(remove_property(property_name)); + return {}; + } + + // 4. If priority is not the empty string and is not an ASCII case-insensitive match for the string "important", then return. + if (!priority.is_empty() && !Infra::is_ascii_case_insensitive_match(priority, "important"sv)) + return {}; + + // 5. Let component value list be the result of parsing value for property property. + auto component_value_list = owner_node().has_value() + ? parse_css_value(CSS::Parser::ParsingParams { owner_node()->element().document() }, value, property_id) + : parse_css_value(CSS::Parser::ParsingParams {}, value, property_id); + + // 6. If component value list is null, then return. + if (!component_value_list) + return {}; + + // 7. Let updated be false. + bool updated = false; + + // 8. If property is a shorthand property, + if (property_is_shorthand(property_id)) { + // then for each longhand property longhand that property maps to, in canonical order, follow these substeps: + StyleComputer::for_each_property_expanding_shorthands(property_id, *component_value_list, StyleComputer::AllowUnresolved::Yes, [this, &updated, priority](PropertyID longhand_property_id, CSSStyleValue const& longhand_value) { + // 1. Let longhand result be the result of set the CSS declaration longhand with the appropriate value(s) from component value list, + // with the important flag set if priority is not the empty string, and unset otherwise, and with the list of declarations being the declarations. + // 2. If longhand result is true, let updated be true. + updated |= set_a_css_declaration(longhand_property_id, longhand_value, !priority.is_empty() ? Important::Yes : Important::No); + }); + } + // 9. Otherwise, + else { + if (property_id == PropertyID::Custom) { + auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); + StyleProperty style_property { + .important = !priority.is_empty() ? Important::Yes : Important::No, + .property_id = property_id, + .value = component_value_list.release_nonnull(), + .custom_name = custom_name, + }; + m_custom_properties.set(custom_name, style_property); + updated = true; + } else { + // let updated be the result of set the CSS declaration property with value component value list, + // with the important flag set if priority is not the empty string, and unset otherwise, + // and with the list of declarations being the declarations. + updated = set_a_css_declaration(property_id, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No); + } + } + + // 10. If updated is true, update style attribute for the CSS declaration block. + if (updated) + update_style_attribute(); + + return {}; } static NonnullRefPtr style_value_for_length_percentage(LengthPercentage const& length_percentage) @@ -159,7 +374,7 @@ static RefPtr style_value_for_shadow(Vector con return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma); } -RefPtr ResolvedCSSStyleDeclaration::style_value_for_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const +RefPtr CSSStyleProperties::style_value_for_computed_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const { auto used_value_for_property = [&layout_node, property_id](Function&& used_value_getter) -> Optional { auto const& display = layout_node.computed_values().display(); @@ -222,18 +437,18 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return CSSColorValue::create_from_color(layout_node.computed_values().background_color(), ColorSyntax::Modern); case PropertyID::BorderBlockEndColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderBottomColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderBottomColor); case PropertyID::BorderBlockStartColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderTopColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderTopColor); case PropertyID::BorderBottomColor: return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color, ColorSyntax::Modern); case PropertyID::BorderInlineEndColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderRightColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderRightColor); case PropertyID::BorderInlineStartColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderLeftColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderLeftColor); case PropertyID::BorderLeftColor: return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color, ColorSyntax::Modern); case PropertyID::BorderRightColor: @@ -250,7 +465,7 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return CSSColorValue::create_from_color(layout_node.computed_values().outline_color(), ColorSyntax::Modern); case PropertyID::TextDecorationColor: return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color(), ColorSyntax::Modern); - // NOTE: text-shadow isn't listed, but is computed the same as box-shadow. + // NB: text-shadow isn't listed, but is computed the same as box-shadow. case PropertyID::TextShadow: return style_value_for_shadow(layout_node.computed_values().text_shadow()); @@ -290,8 +505,8 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert auto writing_mode = layout_node.computed_values().writing_mode(); auto is_vertically_oriented = first_is_one_of(writing_mode, WritingMode::VerticalLr, WritingMode::VerticalRl); if (is_vertically_oriented) - return style_value_for_property(layout_node, PropertyID::Width); - return style_value_for_property(layout_node, PropertyID::Height); + return style_value_for_computed_property(layout_node, PropertyID::Width); + return style_value_for_computed_property(layout_node, PropertyID::Height); } case PropertyID::Height: { auto maybe_used_height = used_value_for_property([](auto const& paintable_box) { return paintable_box.content_height(); }); @@ -303,8 +518,8 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert auto writing_mode = layout_node.computed_values().writing_mode(); auto is_vertically_oriented = first_is_one_of(writing_mode, WritingMode::VerticalLr, WritingMode::VerticalRl); if (is_vertically_oriented) - return style_value_for_property(layout_node, PropertyID::Height); - return style_value_for_property(layout_node, PropertyID::Width); + return style_value_for_computed_property(layout_node, PropertyID::Height); + return style_value_for_computed_property(layout_node, PropertyID::Width); } case PropertyID::MarginBlockEnd: if (auto maybe_used_value = used_value_for_property([&](auto const& paintable_box) { return pixels_for_pixel_box_logical_side(layout_node, paintable_box.box_model().margin, LogicalSide::BlockEnd); }); maybe_used_value.has_value()) @@ -451,8 +666,8 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // A 3x2 transformation matrix, // or a 4x4 matrix where the items m31, m32, m13, m23, m43, m14, m24, m34 are equal to 0 // and m33, m44 are equal to 1. - // NOTE: We only care about 4x4 matrices here. - // NOTE: Our elements are 0-indexed not 1-indexed, and in the opposite order. + // NB: We only care about 4x4 matrices here. + // NB: Our elements are 0-indexed not 1-indexed, and in the opposite order. if (matrix.elements()[0][2] != 0 // m31 || matrix.elements()[1][2] != 0 // m32 || matrix.elements()[2][0] != 0 // m13 @@ -546,135 +761,236 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert StyleValueVector longhand_values; longhand_values.ensure_capacity(longhand_ids.size()); for (auto longhand_id : longhand_ids) - longhand_values.append(style_value_for_property(layout_node, longhand_id).release_nonnull()); + longhand_values.append(style_value_for_computed_property(layout_node, longhand_id).release_nonnull()); return ShorthandStyleValue::create(property_id, move(longhand_ids), move(longhand_values)); } } -Optional ResolvedCSSStyleDeclaration::property(PropertyID property_id) const +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty +WebIDL::ExceptionOr CSSStyleProperties::remove_property(StringView property_name) { - auto& element = owner_node()->element(); - auto pseudo_element = owner_node()->pseudo_element(); + // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. + if (is_readonly()) + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot remove property: CSSStyleProperties is read-only."_string); - // https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle - // NOTE: This is a partial enforcement of step 5 ("If elt is connected, ...") - if (!element.is_connected()) - return {}; + auto property_id = property_id_from_string(property_name); + if (!property_id.has_value()) + return String {}; - auto get_layout_node = [&]() { - if (pseudo_element.has_value()) - return element.get_pseudo_element_node(pseudo_element.value()); - return element.layout_node(); - }; + // 2. If property is not a custom property, let property be property converted to ASCII lowercase. + // NB: We've already converted it to a PropertyID enum value. - Layout::NodeWithStyle* layout_node = get_layout_node(); + // 3. Let value be the return value of invoking getPropertyValue() with property as argument. + auto value = get_property_value(property_name); - // FIXME: Be smarter about updating layout if there's no layout node. - // We may legitimately have no layout node if we're not visible, but this protects against situations - // where we're requesting the computed style before layout has happened. - if (!layout_node || property_affects_layout(property_id)) { - element.document().update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty); - layout_node = get_layout_node(); + // 4. Let removed be false. + bool removed = false; + + // FIXME: 5. If property is a shorthand property, for each longhand property longhand that property maps to: + // 1. If longhand is not a property name of a CSS declaration in the declarations, continue. + // 2. Remove that CSS declaration and let removed be true. + + // 6. Otherwise, if property is a case-sensitive match for a property name of a CSS declaration in the declarations, remove that CSS declaration and let removed be true. + if (property_id == PropertyID::Custom) { + auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); + removed = m_custom_properties.remove(custom_name); } else { - // FIXME: If we had a way to update style for a single element, this would be a good place to use it. - element.document().update_style(); + removed = m_properties.remove_first_matching([&](auto& entry) { return entry.property_id == property_id; }); } - if (!layout_node) { - auto style = element.document().style_computer().compute_style(element, pseudo_element); + // 7. If removed is true, Update style attribute for the CSS declaration block. + if (removed) + update_style_attribute(); - // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. - auto const* value = style->maybe_null_property(property_id); - if (!value) { - dbgln("FIXME: ResolvedCSSStyleDeclaration::property(property_id={:#x}) No value for property ID in newly computed style case.", to_underlying(property_id)); - return {}; - } - return StyleProperty { - .property_id = property_id, - .value = *value, - }; - } - - auto value = style_value_for_property(*layout_node, property_id); - if (!value) - return {}; - return StyleProperty { - .property_id = property_id, - .value = *value, - }; + // 8. Return value. + return value; } -Optional ResolvedCSSStyleDeclaration::custom_property(FlyString const& name) const +// https://www.w3.org/TR/cssom/#serialize-a-css-declaration +static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) { - auto& element = owner_node()->element(); - auto pseudo_element = owner_node()->pseudo_element(); + StringBuilder builder; - element.document().update_style(); + // 1. Let s be the empty string. + // 2. Append property to s. + builder.append(string_from_property_id(property)); - auto const* element_to_check = &element; - while (element_to_check) { - if (auto property = element_to_check->custom_properties(pseudo_element).get(name); property.has_value()) - return *property; + // 3. Append ": " (U+003A U+0020) to s. + builder.append(": "sv); - element_to_check = element_to_check->parent_element(); + // 4. Append value to s. + builder.append(value); + + // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. + if (important == Important::Yes) + builder.append(" !important"sv); + + // 6. Append ";" (U+003B) to s. + builder.append(';'); + + // 7. Return s. + return MUST(builder.to_string()); +} + +// https://www.w3.org/TR/cssom/#serialize-a-css-declaration-block +String CSSStyleProperties::serialized() const +{ + // 1. Let list be an empty array. + Vector list; + + // 2. Let already serialized be an empty array. + HashTable already_serialized; + + // NB: The spec treats custom properties the same as any other property, and expects the above loop to handle them. + // However, our implementation separates them from regular properties, so we need to handle them separately here. + // FIXME: Is the relative order of custom properties and regular properties supposed to be preserved? + for (auto& declaration : m_custom_properties) { + // 1. Let property be declaration’s property name. + auto const& property = declaration.key; + + // 2. If property is in already serialized, continue with the steps labeled declaration loop. + // NB: It is never in already serialized, as there are no shorthands for custom properties. + + // 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. + // NB: There are no shorthands for custom properties. + + // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... + // NB: There are no shorthands for custom properties. + + // 5. Let value be the result of invoking serialize a CSS value of declaration. + auto value = declaration.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); + + // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, + // and the important flag set if declaration has its important flag set. + // NB: We have to inline this here as the actual implementation does not accept custom properties. + String serialized_declaration = [&] { + // https://www.w3.org/TR/cssom/#serialize-a-css-declaration + StringBuilder builder; + + // 1. Let s be the empty string. + // 2. Append property to s. + builder.append(property); + + // 3. Append ": " (U+003A U+0020) to s. + builder.append(": "sv); + + // 4. Append value to s. + builder.append(value); + + // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. + if (declaration.value.important == Important::Yes) + builder.append(" !important"sv); + + // 6. Append ";" (U+003B) to s. + builder.append(';'); + + // 7. Return s. + return MUST(builder.to_string()); + }(); + + // 7. Append serialized declaration to list. + list.append(move(serialized_declaration)); + + // 8. Append property to already serialized. + // NB: We don't need to do this, as we don't have shorthands for custom properties. } + // 3. Declaration loop: For each CSS declaration declaration in declaration block’s declarations, follow these substeps: + for (auto& declaration : m_properties) { + // 1. Let property be declaration’s property name. + auto property = declaration.property_id; + + // 2. If property is in already serialized, continue with the steps labeled declaration loop. + if (already_serialized.contains(property)) + continue; + + // FIXME: 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. + + // FIXME: 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... + + // 5. Let value be the result of invoking serialize a CSS value of declaration. + auto value = declaration.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); + + // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, + // and the important flag set if declaration has its important flag set. + auto serialized_declaration = serialize_a_css_declaration(property, move(value), declaration.important); + + // 7. Append serialized declaration to list. + list.append(move(serialized_declaration)); + + // 8. Append property to already serialized. + already_serialized.set(property); + } + + // 4. Return list joined with " " (U+0020). + StringBuilder builder; + builder.join(' ', list); + return MUST(builder.to_string()); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext +WebIDL::ExceptionOr CSSStyleProperties::set_css_text(StringView css_text) +{ + // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. + if (is_readonly()) { + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties: CSSStyleProperties is read-only."_string); + } + + // 2. Empty the declarations. + // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the declarations, in specified order. + set_declarations_from_text(css_text); + + // 4. Update style attribute for the CSS declaration block. + update_style_attribute(); + return {}; } -static WebIDL::ExceptionOr cannot_modify_computed_property_error(JS::Realm& realm) +// https://drafts.csswg.org/cssom/#set-a-css-declaration +bool CSSStyleProperties::set_a_css_declaration(PropertyID property_id, NonnullRefPtr value, Important important) { - return WebIDL::NoModificationAllowedError::create(realm, "Cannot modify properties in result of getComputedStyle()"_string); + VERIFY(!is_computed()); + + // FIXME: Handle logical property groups. + + for (auto& property : m_properties) { + if (property.property_id == property_id) { + if (property.important == important && *property.value == *value) + return false; + property.value = move(value); + property.important = important; + return true; + } + } + + m_properties.append(StyleProperty { + .important = important, + .property_id = property_id, + .value = move(value), + }); + return true; } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_property(PropertyID, StringView, StringView) +void CSSStyleProperties::empty_the_declarations() { - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_modify_computed_property_error(realm()); + m_properties.clear(); + m_custom_properties.clear(); } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_property(StringView, StringView, StringView) +void CSSStyleProperties::set_the_declarations(Vector properties, HashMap custom_properties) { - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_modify_computed_property_error(realm()); + m_properties = move(properties); + m_custom_properties = move(custom_properties); } -static WebIDL::ExceptionOr cannot_remove_computed_property_error(JS::Realm& realm) +void CSSStyleProperties::set_declarations_from_text(StringView css_text) { - return WebIDL::NoModificationAllowedError::create(realm, "Cannot remove properties from result of getComputedStyle()"_string); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::remove_property(PropertyID) -{ - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_remove_computed_property_error(realm()); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::remove_property(StringView) -{ - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_remove_computed_property_error(realm()); -} - -String ResolvedCSSStyleDeclaration::serialized() const -{ - // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-csstext - // If the computed flag is set, then return the empty string. - - // NOTE: ResolvedCSSStyleDeclaration is something you would only get from window.getComputedStyle(), - // which returns what the spec calls "resolved style". The "computed flag" is always set here. - return String {}; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_css_text(StringView) -{ - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()"_string); + empty_the_declarations(); + auto parsing_params = owner_node().has_value() + ? Parser::ParsingParams(owner_node()->element().document()) + : Parser::ParsingParams(); + auto style = parse_css_style_attribute(parsing_params, css_text); + set_the_declarations(style.properties, style.custom_properties); } } diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.h b/Libraries/LibWeb/CSS/CSSStyleProperties.h new file mode 100644 index 00000000000..03fff5dbe64 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2023, Andreas Kling + * Copyright (c) 2024-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/cssom/#cssstyleproperties +class CSSStyleProperties : public CSSStyleDeclaration { + WEB_PLATFORM_OBJECT(CSSStyleProperties, CSSStyleDeclaration); + GC_DECLARE_ALLOCATOR(CSSStyleProperties); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, Vector, HashMap custom_properties); + + [[nodiscard]] static GC::Ref create_resolved_style(DOM::ElementReference); + [[nodiscard]] static GC::Ref create_element_inline_style(DOM::ElementReference, Vector, HashMap custom_properties); + + virtual ~CSSStyleProperties() override = default; + virtual void initialize(JS::Realm&) override; + + virtual size_t length() const override; + virtual String item(size_t index) const override; + + virtual Optional property(PropertyID) const override; + virtual Optional custom_property(FlyString const& custom_property_name) const override; + + // Temporary for one commit. + using Base::remove_property, Base::set_property; + + virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; + virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; + Vector const& properties() const { return m_properties; } + HashMap const& custom_properties() const { return m_custom_properties; } + + size_t custom_property_count() const { return m_custom_properties.size(); } + + virtual String serialized() const final override; + virtual WebIDL::ExceptionOr set_css_text(StringView) override; + + void set_declarations_from_text(StringView); + +private: + CSSStyleProperties(JS::Realm&, Computed, Readonly, Vector properties, HashMap custom_properties, Optional); + + virtual void visit_edges(Cell::Visitor&) override; + + RefPtr style_value_for_computed_property(Layout::NodeWithStyle const&, PropertyID) const; + + bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); + void empty_the_declarations(); + void set_the_declarations(Vector properties, HashMap custom_properties); + + Vector m_properties; + HashMap m_custom_properties; +}; + +} diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.idl b/Libraries/LibWeb/CSS/CSSStyleProperties.idl new file mode 100644 index 00000000000..22db5036419 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.idl @@ -0,0 +1,6 @@ +#import + +// https://drafts.csswg.org/cssom/#cssstyleproperties +[Exposed=Window] +interface CSSStyleProperties : CSSStyleDeclaration { +}; diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.cpp b/Libraries/LibWeb/CSS/CSSStyleRule.cpp index ee568cc70f1..010953d791c 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleRule.cpp @@ -17,12 +17,12 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSStyleRule); -GC::Ref CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, PropertyOwningCSSStyleDeclaration& declaration, CSSRuleList& nested_rules) +GC::Ref CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules) { return realm.create(realm, move(selectors), declaration, nested_rules); } -CSSStyleRule::CSSStyleRule(JS::Realm& realm, SelectorList&& selectors, PropertyOwningCSSStyleDeclaration& declaration, CSSRuleList& nested_rules) +CSSStyleRule::CSSStyleRule(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules) : CSSGroupingRule(realm, nested_rules, Type::Style) , m_selectors(move(selectors)) , m_declaration(declaration) @@ -43,7 +43,7 @@ void CSSStyleRule::visit_edges(Cell::Visitor& visitor) } // https://drafts.csswg.org/cssom-1/#dom-cssstylerule-style -CSSStyleDeclaration* CSSStyleRule::style() +CSSStyleProperties* CSSStyleRule::style() { return m_declaration; } diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.h b/Libraries/LibWeb/CSS/CSSStyleRule.h index 1370600e5f4..572be8bd494 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.h +++ b/Libraries/LibWeb/CSS/CSSStyleRule.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include namespace Web::CSS { @@ -19,23 +19,23 @@ class CSSStyleRule final : public CSSGroupingRule { GC_DECLARE_ALLOCATOR(CSSStyleRule); public: - [[nodiscard]] static GC::Ref create(JS::Realm&, SelectorList&&, PropertyOwningCSSStyleDeclaration&, CSSRuleList&); + [[nodiscard]] static GC::Ref create(JS::Realm&, SelectorList&&, CSSStyleProperties&, CSSRuleList&); virtual ~CSSStyleRule() override = default; SelectorList const& selectors() const { return m_selectors; } SelectorList const& absolutized_selectors() const; - PropertyOwningCSSStyleDeclaration const& declaration() const { return m_declaration; } + CSSStyleProperties const& declaration() const { return m_declaration; } String selector_text() const; void set_selector_text(StringView); - CSSStyleDeclaration* style(); + CSSStyleProperties* style(); [[nodiscard]] FlyString const& qualified_layer_name() const { return parent_layer_internal_qualified_name(); } private: - CSSStyleRule(JS::Realm&, SelectorList&&, PropertyOwningCSSStyleDeclaration&, CSSRuleList&); + CSSStyleRule(JS::Realm&, SelectorList&&, CSSStyleProperties&, CSSRuleList&); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -46,7 +46,7 @@ private: SelectorList m_selectors; mutable Optional m_cached_absolutized_selectors; - GC::Ref m_declaration; + GC::Ref m_declaration; }; template<> diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.idl b/Libraries/LibWeb/CSS/CSSStyleRule.idl index 1b61834b5d1..f7df36c9824 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.idl +++ b/Libraries/LibWeb/CSS/CSSStyleRule.idl @@ -1,9 +1,9 @@ #import -#import +#import // https://drafts.csswg.org/cssom/#the-cssstylerule-interface [Exposed=Window] interface CSSStyleRule : CSSGroupingRule { attribute CSSOMString selectorText; - [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; + [SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style; }; diff --git a/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl b/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl index 15147bad4ac..8a09f8f5e02 100644 --- a/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl +++ b/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl @@ -1,6 +1,6 @@ -#import +#import // https://w3c.github.io/csswg-drafts/cssom/#elementcssinlinestyle interface mixin ElementCSSInlineStyle { - [SameObject, PutForwards=cssText, ImplementedAs=style_for_bindings] readonly attribute CSSStyleDeclaration style; + [SameObject, PutForwards=cssText, ImplementedAs=style_for_bindings] readonly attribute CSSStyleProperties style; }; diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 731fbfcdfd9..3e4d4c00a6e 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -1492,14 +1493,14 @@ void Parser::extract_property(Declaration const& declaration, PropertiesAndCusto } } -PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector const& declarations) +CSSStyleProperties* Parser::convert_to_style_declaration(Vector const& declarations) { PropertiesAndCustomProperties properties; PropertiesAndCustomProperties& dest = properties; for (auto const& declaration : declarations) { extract_property(declaration, dest); } - return PropertyOwningCSSStyleDeclaration::create(realm(), move(properties.properties), move(properties.custom_properties)); + return CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); } Optional Parser::convert_to_style_property(Declaration const& declaration) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 50391d0343f..7c74ac7a938 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -245,7 +245,7 @@ private: GC::Ptr convert_to_supports_rule(AtRule const&, Nested); GC::Ptr convert_to_property_rule(AtRule const& rule); - PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector const&); + CSSStyleProperties* convert_to_style_declaration(Vector const&); Optional convert_to_style_property(Declaration const&); Optional parse_dimension(ComponentValue const&); diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index 6a669113d04..883a1bde1d4 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * Copyright (c) 2021, Tobias Christiansen * Copyright (c) 2022, MacDue * Copyright (c) 2024, Shannon Booth @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -390,7 +391,7 @@ GC::Ptr Parser::convert_to_keyframes_rule(AtRule const& rule) qualified_rule.for_each_as_declaration_list([&](auto const& declaration) { extract_property(declaration, properties); }); - auto style = PropertyOwningCSSStyleDeclaration::create(realm(), move(properties.properties), move(properties.custom_properties)); + auto style = CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); for (auto& selector : selectors) { auto keyframe_rule = CSSKeyframeRule::create(realm(), selector, *style); keyframes.append(keyframe_rule); diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h deleted file mode 100644 index 7743ab02751..00000000000 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021-2023, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace Web::CSS { - -class ResolvedCSSStyleDeclaration final : public CSSStyleDeclaration { - WEB_PLATFORM_OBJECT(ResolvedCSSStyleDeclaration, CSSStyleDeclaration); - GC_DECLARE_ALLOCATOR(ResolvedCSSStyleDeclaration); - -public: - [[nodiscard]] static GC::Ref create(DOM::Element&, Optional = {}); - - virtual ~ResolvedCSSStyleDeclaration() override = default; - - virtual size_t length() const override; - virtual String item(size_t index) const override; - - virtual Optional property(PropertyID) const override; - virtual Optional custom_property(FlyString const& custom_property_name) const override; - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr remove_property(PropertyID) override; - virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; - - virtual String serialized() const override; - virtual WebIDL::ExceptionOr set_css_text(StringView) override; - -private: - explicit ResolvedCSSStyleDeclaration(DOM::Element&, Optional); - - RefPtr style_value_for_property(Layout::NodeWithStyle const&, PropertyID) const; -}; - -} diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index b1a8c8805af..75cd39d664f 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2023, Andreas Kling * Copyright (c) 2021, the SerenityOS developers. - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * Copyright (c) 2024, Matthew Olsson * * SPDX-License-Identifier: BSD-2-Clause @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -122,7 +123,7 @@ struct Traits : public DefaultTraitstype() == CSSRule::Type::Style) return static_cast(*rule).declaration(); @@ -2868,7 +2869,7 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame resolved_keyframe; auto key = static_cast(keyframe.key().value() * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor); - auto const& keyframe_style = *keyframe.style_as_property_owning_style_declaration(); + auto const& keyframe_style = *keyframe.style(); for (auto const& it : keyframe_style.properties()) { // Unresolved properties will be resolved in collect_animation_into() for_each_property_expanding_shorthands(it.property_id, it.value, AllowUnresolved::Yes, [&](PropertyID shorthand_id, CSSStyleValue const& shorthand_value) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index 972114c664e..605c5913b3a 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -89,7 +89,7 @@ struct MatchingRule { bool must_be_hovered { false }; // Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations - PropertyOwningCSSStyleDeclaration const& declaration() const; + CSSStyleProperties const& declaration() const; SelectorList const& absolutized_selectors() const; FlyString const& qualified_layer_name() const; }; diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 10f137cd7f8..07ed6c07b7e 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -14,10 +14,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -649,7 +649,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style() GC::Ref Element::resolved_css_values(Optional type) { - auto element_computed_style = CSS::ResolvedCSSStyleDeclaration::create(*this, type); + auto element_computed_style = CSS::CSSStyleProperties::create_resolved_style({ *this, type }); auto properties = heap().allocate(); for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) { @@ -918,10 +918,10 @@ void Element::set_shadow_root(GC::Ptr shadow_root) invalidate_style(StyleInvalidationReason::ElementSetShadowRoot); } -CSS::CSSStyleDeclaration* Element::style_for_bindings() +CSS::CSSStyleProperties* Element::style_for_bindings() { if (!m_inline_style) - m_inline_style = CSS::PropertyOwningCSSStyleDeclaration::create_element_inline_style(realm(), *this, {}, {}); + m_inline_style = CSS::CSSStyleProperties::create_element_inline_style({ *this }, {}, {}); return m_inline_style; } @@ -3524,7 +3524,7 @@ void Element::attribute_changed(FlyString const& local_name, Optional co if (m_inline_style && m_inline_style->is_updating()) return; if (!m_inline_style) - m_inline_style = CSS::PropertyOwningCSSStyleDeclaration::create_element_inline_style(realm(), *this, {}, {}); + m_inline_style = CSS::CSSStyleProperties::create_element_inline_style({ *this }, {}, {}); m_inline_style->set_declarations_from_text(*value); set_needs_style_update(true); } diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index d2fba9fe53e..da314eda2e3 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -211,10 +211,10 @@ public: void reset_animated_css_properties(); - GC::Ptr inline_style() { return m_inline_style; } - GC::Ptr inline_style() const { return m_inline_style; } + GC::Ptr inline_style() { return m_inline_style; } + GC::Ptr inline_style() const { return m_inline_style; } - CSS::CSSStyleDeclaration* style_for_bindings(); + CSS::CSSStyleProperties* style_for_bindings(); CSS::StyleSheetList& document_or_shadow_root_style_sheets(); @@ -499,7 +499,7 @@ private: FlyString m_html_uppercased_qualified_name; GC::Ptr m_attributes; - GC::Ptr m_inline_style; + GC::Ptr m_inline_style; GC::Ptr m_class_list; GC::Ptr m_shadow_root; diff --git a/Libraries/LibWeb/Dump.cpp b/Libraries/LibWeb/Dump.cpp index 8a69e21ee92..6a870f1d8c0 100644 --- a/Libraries/LibWeb/Dump.cpp +++ b/Libraries/LibWeb/Dump.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2023, Andreas Kling * Copyright (c) 2021, the SerenityOS developers. - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -834,7 +835,7 @@ void dump_property_rule(StringBuilder& builder, CSS::CSSPropertyRule const& prop } } -void dump_declaration(StringBuilder& builder, CSS::PropertyOwningCSSStyleDeclaration const& declaration, int indent_levels) +void dump_style_properties(StringBuilder& builder, CSS::CSSStyleProperties const& declaration, int indent_levels) { indent(builder, indent_levels); builder.appendff("Declarations ({}):\n", declaration.length()); @@ -859,7 +860,7 @@ void dump_style_rule(StringBuilder& builder, CSS::CSSStyleRule const& rule, int for (auto& selector : rule.selectors()) { dump_selector(builder, selector, indent_levels + 1); } - dump_declaration(builder, rule.declaration(), indent_levels + 1); + dump_style_properties(builder, rule.declaration(), indent_levels + 1); indent(builder, indent_levels); builder.appendff(" Child rules ({}):\n", rule.css_rules().length()); @@ -951,6 +952,6 @@ void dump_nested_declarations(StringBuilder& builder, CSS::CSSNestedDeclarations { indent(builder, indent_levels); builder.append(" Nested declarations:\n"sv); - dump_declaration(builder, declarations.declaration(), indent_levels + 1); + dump_style_properties(builder, declarations.declaration(), indent_levels + 1); } } diff --git a/Libraries/LibWeb/Dump.h b/Libraries/LibWeb/Dump.h index acb6d3ebae2..9fd7746f888 100644 --- a/Libraries/LibWeb/Dump.h +++ b/Libraries/LibWeb/Dump.h @@ -24,7 +24,7 @@ void dump_sheet(StringBuilder&, CSS::StyleSheet const&); void dump_sheet(CSS::StyleSheet const&); void dump_rule(StringBuilder&, CSS::CSSRule const&, int indent_levels = 0); void dump_rule(CSS::CSSRule const&); -void dump_declaration(StringBuilder&, CSS::PropertyOwningCSSStyleDeclaration const&, int indent_levels = 0); +void dump_style_properties(StringBuilder&, CSS::CSSStyleProperties const&, int indent_levels = 0); void dump_font_face_rule(StringBuilder&, CSS::CSSFontFaceRule const&, int indent_levels = 0); void dump_import_rule(StringBuilder&, CSS::CSSImportRule const&, int indent_levels = 0); void dump_media_rule(StringBuilder&, CSS::CSSMediaRule const&, int indent_levels = 0); diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index edf02ac0270..f5680fbb69c 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -4757,7 +4756,7 @@ Optional> resolved_value(GC::Ref(*element)); + auto resolved_css_style_declaration = CSS::CSSStyleProperties::create_resolved_style({ static_cast(*element) }); auto optional_style_property = resolved_css_style_declaration->property(property_id); if (!optional_style_property.has_value()) return {}; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index b3f539a1089..a0b40c9e817 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -179,6 +179,7 @@ class CSSRGB; class CSSRule; class CSSRuleList; class CSSStyleDeclaration; +class CSSStyleProperties; class CSSStyleRule; class CSSStyleSheet; class CSSStyleValue; @@ -235,7 +236,6 @@ class Percentage; class PercentageOrCalculated; class PercentageStyleValue; class PositionStyleValue; -class PropertyOwningCSSStyleDeclaration; class RadialGradientStyleValue; class Ratio; class RatioStyleValue; diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 8be002b9b77..3fae3a9bee3 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -1208,7 +1207,7 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme // 1. Let doc be elt’s node document. // 2. Let obj be elt. - Optional obj_pseudo; + DOM::ElementReference object { element }; // 3. If pseudoElt is provided, is not the empty string, and starts with a colon, then: if (pseudo_element.has_value() && pseudo_element.value().starts_with(':')) { @@ -1216,22 +1215,18 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme auto type = parse_pseudo_element_selector(CSS::Parser::ParsingParams(associated_document()), pseudo_element.value()); // 2. If type is failure, or is an ::slotted() or ::part() pseudo-element, let obj be null. - // FIXME: We can't pass a null element to ResolvedCSSStyleDeclaration + // FIXME: We can't pass a null element to CSSStyleProperties::create_resolved_style() if (!type.has_value()) { } // 3. Otherwise let obj be the given pseudo-element of elt. else { // TODO: Keep the function arguments of the pseudo-element if there are any. - obj_pseudo = type.value().type(); + object = { element, type.value().type() }; } } - // AD-HOC: Just return a ResolvedCSSStyleDeclaration because that's what we have for now. - // FIXME: Implement CSSStyleProperties, and then follow the rest of these steps instead. - return realm().create(element, obj_pseudo); - + // FIXME: Implement steps 4 and 5 when we can. // 4. Let decls be an empty list of CSS declarations. - // 5. If obj is not null, and elt is connected, part of the flat tree, and its shadow-including root // has a browsing context which either doesn’t have a browsing context container, or whose browsing // context container is being rendered, set decls to a list of all longhand properties that are @@ -1250,6 +1245,7 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme // Null. // owner node // obj. + return CSS::CSSStyleProperties::create_resolved_style(move(object)); } // https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-matchmedia diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index d12820fbe1e..1ca79bc8591 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -38,6 +38,7 @@ libweb_js_bindings(CSS/CSSPropertyRule) libweb_js_bindings(CSS/CSSRule) libweb_js_bindings(CSS/CSSRuleList) libweb_js_bindings(CSS/CSSStyleDeclaration) +libweb_js_bindings(CSS/CSSStyleProperties) libweb_js_bindings(CSS/CSSStyleRule) libweb_js_bindings(CSS/CSSStyleSheet) libweb_js_bindings(CSS/CSSSupportsRule) diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn index b539f388a50..680c1e9e397 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn @@ -26,6 +26,7 @@ source_set("CSS") { "CSSRule.cpp", "CSSRuleList.cpp", "CSSStyleDeclaration.cpp", + "CSSStyleProperties.cpp", "CSSStyleRule.cpp", "CSSStyleSheet.cpp", "CSSStyleValue.cpp", @@ -57,7 +58,6 @@ source_set("CSS") { "PreferredMotion.cpp", "Ratio.cpp", "Resolution.cpp", - "ResolvedCSSStyleDeclaration.cpp", "Screen.cpp", "ScreenOrientation.cpp", "Selector.cpp", diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 787ba16db73..df057954b11 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -51,6 +51,7 @@ CSSPropertyRule CSSRule CSSRuleList CSSStyleDeclaration +CSSStyleProperties CSSStyleRule CSSStyleSheet CSSSupportsRule From a28197669a9899d41ea63c06c39c9aa028ad091a Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 14:07:08 +0000 Subject: [PATCH 078/141] LibWeb/CSS: Move property code from CSSStyleDeclaration to *Properties CSSStyleDeclaration is a base class that's used by various collections of style properties or descriptors. This commit moves all style-property-related code into CSSStyleProperties, where it belongs. As noted in the previous commit, we also apply the CSSStyleProperties prototype now. --- Documentation/CSSProperties.md | 2 +- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 243 ------------------ Libraries/LibWeb/CSS/CSSStyleDeclaration.h | 21 +- Libraries/LibWeb/CSS/CSSStyleDeclaration.idl | 8 - Libraries/LibWeb/CSS/CSSStyleProperties.cpp | 241 ++++++++++++++++- Libraries/LibWeb/CSS/CSSStyleProperties.h | 24 +- Libraries/LibWeb/CSS/CSSStyleProperties.idl | 4 + .../LibWeb/GenerateCSSStyleProperties.cpp | 10 +- .../getClientRects-in-detached-document.txt | 2 +- ...pported-properties-and-default-values.txt} | 12 +- .../css/style-declaration-parent-rule.txt | 2 +- ...ported-properties-and-default-values.html} | 2 +- 12 files changed, 276 insertions(+), 295 deletions(-) rename Tests/LibWeb/Text/expected/css/{CSSStyleDeclaration-all-supported-properties-and-default-values.txt => CSSStyleProperties-all-supported-properties-and-default-values.txt} (97%) rename Tests/LibWeb/Text/input/css/{CSSStyleDeclaration-all-supported-properties-and-default-values.html => CSSStyleProperties-all-supported-properties-and-default-values.html} (91%) diff --git a/Documentation/CSSProperties.md b/Documentation/CSSProperties.md index d2c26def7c3..9c105aadef0 100644 --- a/Documentation/CSSProperties.md +++ b/Documentation/CSSProperties.md @@ -60,4 +60,4 @@ bool Paintable::is_visible() const Some properties have special rules for getting the computed value from JS. For these, you will need to add to `CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are constructed in an unusual way (as in, not -using `ShorthandStyleValue`) also need handling inside `CSSStyleDeclaration::get_property_internal()`. +using `ShorthandStyleValue`) also need handling inside `CSSStyleProperties::get_property_internal()`. diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 4860098c687..c562ae3f90e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -9,12 +9,7 @@ #include #include #include -#include #include -#include -#include -#include -#include #include #include @@ -59,230 +54,6 @@ void CSSStyleDeclaration::update_style_attribute() set_is_updating(false); } -static Optional style_property_for_sided_shorthand(PropertyID property_id, Optional const& top, Optional const& right, Optional const& bottom, Optional const& left) -{ - if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value()) - return {}; - - if (top->important != right->important || top->important != bottom->important || top->important != left->important) - return {}; - - ValueComparingNonnullRefPtr const top_value { top->value }; - ValueComparingNonnullRefPtr const right_value { right->value }; - ValueComparingNonnullRefPtr const bottom_value { bottom->value }; - ValueComparingNonnullRefPtr const left_value { left->value }; - - bool const top_and_bottom_same = top_value == bottom_value; - bool const left_and_right_same = left_value == right_value; - - RefPtr value; - - if (top_and_bottom_same && left_and_right_same && top_value == left_value) { - value = top_value; - } else if (top_and_bottom_same && left_and_right_same) { - value = StyleValueList::create(StyleValueVector { top_value, right_value }, StyleValueList::Separator::Space); - } else if (left_and_right_same) { - value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value }, StyleValueList::Separator::Space); - } else { - value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value, left_value }, StyleValueList::Separator::Space); - } - - return StyleProperty { - .important = top->important, - .property_id = property_id, - .value = value.release_nonnull(), - }; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue -Optional CSSStyleDeclaration::get_property_internal(PropertyID property_id) const -{ - // 2. If property is a shorthand property, then follow these substeps: - if (property_is_shorthand(property_id)) { - - // AD-HOC: Handle shorthands that require manual construction. - switch (property_id) { - case PropertyID::Border: { - auto width = get_property_internal(PropertyID::BorderWidth); - auto style = get_property_internal(PropertyID::BorderStyle); - auto color = get_property_internal(PropertyID::BorderColor); - // `border` only has a reasonable value if all four sides are the same. - if (!width.has_value() || width->value->is_value_list() || !style.has_value() || style->value->is_value_list() || !color.has_value() || color->value->is_value_list()) - return {}; - if (width->important != style->important || width->important != color->important) - return {}; - return StyleProperty { - .important = width->important, - .property_id = property_id, - .value = ShorthandStyleValue::create(property_id, - { PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor }, - { width->value, style->value, color->value }) - }; - } - case PropertyID::BorderColor: { - auto top = get_property_internal(PropertyID::BorderTopColor); - auto right = get_property_internal(PropertyID::BorderRightColor); - auto bottom = get_property_internal(PropertyID::BorderBottomColor); - auto left = get_property_internal(PropertyID::BorderLeftColor); - return style_property_for_sided_shorthand(property_id, top, right, bottom, left); - } - case PropertyID::BorderStyle: { - auto top = get_property_internal(PropertyID::BorderTopStyle); - auto right = get_property_internal(PropertyID::BorderRightStyle); - auto bottom = get_property_internal(PropertyID::BorderBottomStyle); - auto left = get_property_internal(PropertyID::BorderLeftStyle); - return style_property_for_sided_shorthand(property_id, top, right, bottom, left); - } - case PropertyID::BorderWidth: { - auto top = get_property_internal(PropertyID::BorderTopWidth); - auto right = get_property_internal(PropertyID::BorderRightWidth); - auto bottom = get_property_internal(PropertyID::BorderBottomWidth); - auto left = get_property_internal(PropertyID::BorderLeftWidth); - return style_property_for_sided_shorthand(property_id, top, right, bottom, left); - } - case PropertyID::FontVariant: { - auto ligatures = get_property_internal(PropertyID::FontVariantLigatures); - auto caps = get_property_internal(PropertyID::FontVariantCaps); - auto alternates = get_property_internal(PropertyID::FontVariantAlternates); - auto numeric = get_property_internal(PropertyID::FontVariantNumeric); - auto east_asian = get_property_internal(PropertyID::FontVariantEastAsian); - auto position = get_property_internal(PropertyID::FontVariantPosition); - auto emoji = get_property_internal(PropertyID::FontVariantEmoji); - - if (!ligatures.has_value() || !caps.has_value() || !alternates.has_value() || !numeric.has_value() || !east_asian.has_value() || !position.has_value() || !emoji.has_value()) - return {}; - - if (ligatures->important != caps->important || ligatures->important != alternates->important || ligatures->important != numeric->important || ligatures->important != east_asian->important || ligatures->important != position->important || ligatures->important != emoji->important) - return {}; - - // If ligatures is `none` and any other value isn't `normal`, that's invalid. - if (ligatures->value->to_keyword() == Keyword::None - && (caps->value->to_keyword() != Keyword::Normal - || alternates->value->to_keyword() != Keyword::Normal - || numeric->value->to_keyword() != Keyword::Normal - || east_asian->value->to_keyword() != Keyword::Normal - || position->value->to_keyword() != Keyword::Normal - || emoji->value->to_keyword() != Keyword::Normal)) { - return {}; - } - - return StyleProperty { - .important = ligatures->important, - .property_id = property_id, - .value = ShorthandStyleValue::create(property_id, - { PropertyID::FontVariantLigatures, PropertyID::FontVariantCaps, PropertyID::FontVariantAlternates, PropertyID::FontVariantNumeric, PropertyID::FontVariantEastAsian, PropertyID::FontVariantPosition, PropertyID::FontVariantEmoji }, - { ligatures->value, caps->value, alternates->value, numeric->value, east_asian->value, position->value, emoji->value }) - }; - } - case PropertyID::Margin: { - auto top = get_property_internal(PropertyID::MarginTop); - auto right = get_property_internal(PropertyID::MarginRight); - auto bottom = get_property_internal(PropertyID::MarginBottom); - auto left = get_property_internal(PropertyID::MarginLeft); - return style_property_for_sided_shorthand(property_id, top, right, bottom, left); - } - case PropertyID::Padding: { - auto top = get_property_internal(PropertyID::PaddingTop); - auto right = get_property_internal(PropertyID::PaddingRight); - auto bottom = get_property_internal(PropertyID::PaddingBottom); - auto left = get_property_internal(PropertyID::PaddingLeft); - return style_property_for_sided_shorthand(property_id, top, right, bottom, left); - } - default: - break; - } - - // 1. Let list be a new empty array. - Vector> list; - Optional last_important_flag; - - // 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps: - Vector longhand_ids = longhands_for_shorthand(property_id); - for (auto longhand_property_id : longhand_ids) { - // 1. If longhand is a case-sensitive match for a property name of a CSS declaration in the declarations, - // let declaration be that CSS declaration, or null otherwise. - auto declaration = get_property_internal(longhand_property_id); - - // 2. If declaration is null, then return the empty string. - if (!declaration.has_value()) - return {}; - - // 3. Append the declaration to list. - list.append(declaration->value); - - if (last_important_flag.has_value() && declaration->important != *last_important_flag) - return {}; - last_important_flag = declaration->important; - } - - // 3. If important flags of all declarations in list are same, then return the serialization of list. - // NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string(). - return StyleProperty { - .important = last_important_flag.value(), - .property_id = property_id, - .value = ShorthandStyleValue::create(property_id, longhand_ids, list), - }; - - // 4. Return the empty string. - // NOTE: This is handled by the loop. - } - - return property(property_id); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue -String CSSStyleDeclaration::get_property_value(StringView property_name) const -{ - auto property_id = property_id_from_string(property_name); - if (!property_id.has_value()) - return {}; - - if (property_id.value() == PropertyID::Custom) { - auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes())); - if (maybe_custom_property.has_value()) { - return maybe_custom_property.value().value->to_string( - is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue - : CSSStyleValue::SerializationMode::Normal); - } - return {}; - } - - auto maybe_property = get_property_internal(property_id.value()); - if (!maybe_property.has_value()) - return {}; - return maybe_property->value->to_string( - is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue - : CSSStyleValue::SerializationMode::Normal); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority -StringView CSSStyleDeclaration::get_property_priority(StringView property_name) const -{ - auto property_id = property_id_from_string(property_name); - if (!property_id.has_value()) - return {}; - if (property_id.value() == PropertyID::Custom) { - auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes())); - if (!maybe_custom_property.has_value()) - return {}; - return maybe_custom_property.value().important == Important::Yes ? "important"sv : ""sv; - } - auto maybe_property = property(property_id.value()); - if (!maybe_property.has_value()) - return {}; - return maybe_property->important == Important::Yes ? "important"sv : ""sv; -} - -WebIDL::ExceptionOr CSSStyleDeclaration::set_property(PropertyID property_id, StringView css_text, StringView priority) -{ - return set_property(string_from_property_id(property_id), css_text, priority); -} - -WebIDL::ExceptionOr CSSStyleDeclaration::remove_property(PropertyID property_name) -{ - return remove_property(string_from_property_id(property_name)); -} - // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext String CSSStyleDeclaration::css_text() const { @@ -294,20 +65,6 @@ String CSSStyleDeclaration::css_text() const return serialized(); } -// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat -String CSSStyleDeclaration::css_float() const -{ - // The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument. - return get_property_value("float"sv); -} - -WebIDL::ExceptionOr CSSStyleDeclaration::set_css_float(StringView value) -{ - // On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value, - // and no third argument. Any exceptions thrown must be re-thrown. - return set_property("float"sv, value, ""sv); -} - Optional CSSStyleDeclaration::item_value(size_t index) const { auto value = item(index); diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index 5e039e26107..7b4f63eddab 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -8,10 +8,8 @@ #pragma once #include -#include #include #include -#include #include #include @@ -19,8 +17,7 @@ namespace Web::CSS { // https://drafts.csswg.org/cssom/#css-declaration-blocks class CSSStyleDeclaration - : public Bindings::PlatformObject - , public Bindings::GeneratedCSSStyleProperties { + : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(CSSStyleDeclaration, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(CSSStyleDeclaration); @@ -31,24 +28,15 @@ public: virtual size_t length() const = 0; virtual String item(size_t index) const = 0; - virtual Optional property(PropertyID) const = 0; - virtual Optional custom_property(FlyString const& custom_property_name) const = 0; - - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority = ""sv); - virtual WebIDL::ExceptionOr remove_property(PropertyID); - virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) = 0; virtual WebIDL::ExceptionOr remove_property(StringView property_name) = 0; - String get_property_value(StringView property) const; - StringView get_property_priority(StringView property) const; + virtual String get_property_value(StringView property_name) const = 0; + virtual StringView get_property_priority(StringView property_name) const = 0; String css_text() const; virtual WebIDL::ExceptionOr set_css_text(StringView) = 0; - String css_float() const; - WebIDL::ExceptionOr set_css_float(StringView); - virtual String serialized() const = 0; // https://drafts.csswg.org/cssom/#cssstyledeclaration-computed-flag @@ -82,14 +70,11 @@ protected: virtual void visit_edges(Visitor&) override; - virtual CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() override { return *this; } - void update_style_attribute(); private: // ^PlatformObject virtual Optional item_value(size_t index) const override; - Optional get_property_internal(PropertyID) const; // https://drafts.csswg.org/cssom/#cssstyledeclaration-parent-css-rule GC::Ptr m_parent_rule { nullptr }; diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl b/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl index 8918d8fe51e..95c87cbd7db 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl @@ -1,9 +1,6 @@ -#import - // https://drafts.csswg.org/cssom/#cssstyledeclaration [Exposed=Window] interface CSSStyleDeclaration { - [CEReactions] attribute CSSOMString cssText; readonly attribute unsigned long length; @@ -16,9 +13,4 @@ interface CSSStyleDeclaration { [CEReactions] CSSOMString removeProperty(CSSOMString property); readonly attribute CSSRule? parentRule; - - [CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat; - }; - -CSSStyleDeclaration includes GeneratedCSSStyleProperties; diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp index aadb05a82d3..93f6a1dee41 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -78,8 +78,7 @@ CSSStyleProperties::CSSStyleProperties(JS::Realm& realm, Computed computed, Read void CSSStyleProperties::initialize(JS::Realm& realm) { Base::initialize(realm); - // Temporarily disabled for a single commit to make the changes cleaner. - // WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties); + WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties); } void CSSStyleProperties::visit_edges(Visitor& visitor) @@ -278,6 +277,11 @@ WebIDL::ExceptionOr CSSStyleProperties::set_property(StringView property_n return {}; } +WebIDL::ExceptionOr CSSStyleProperties::set_property(PropertyID property_id, StringView css_text, StringView priority) +{ + return set_property(string_from_property_id(property_id), css_text, priority); +} + static NonnullRefPtr style_value_for_length_percentage(LengthPercentage const& length_percentage) { if (length_percentage.is_auto()) @@ -374,6 +378,220 @@ static RefPtr style_value_for_shadow(Vector con return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma); } +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue +String CSSStyleProperties::get_property_value(StringView property_name) const +{ + auto property_id = property_id_from_string(property_name); + if (!property_id.has_value()) + return {}; + + if (property_id.value() == PropertyID::Custom) { + auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes())); + if (maybe_custom_property.has_value()) { + return maybe_custom_property.value().value->to_string( + is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue + : CSSStyleValue::SerializationMode::Normal); + } + return {}; + } + + auto maybe_property = get_property_internal(property_id.value()); + if (!maybe_property.has_value()) + return {}; + return maybe_property->value->to_string( + is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue + : CSSStyleValue::SerializationMode::Normal); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority +StringView CSSStyleProperties::get_property_priority(StringView property_name) const +{ + auto property_id = property_id_from_string(property_name); + if (!property_id.has_value()) + return {}; + if (property_id.value() == PropertyID::Custom) { + auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes())); + if (!maybe_custom_property.has_value()) + return {}; + return maybe_custom_property.value().important == Important::Yes ? "important"sv : ""sv; + } + auto maybe_property = property(property_id.value()); + if (!maybe_property.has_value()) + return {}; + return maybe_property->important == Important::Yes ? "important"sv : ""sv; +} + +static Optional style_property_for_sided_shorthand(PropertyID property_id, Optional const& top, Optional const& right, Optional const& bottom, Optional const& left) +{ + if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value()) + return {}; + + if (top->important != right->important || top->important != bottom->important || top->important != left->important) + return {}; + + ValueComparingNonnullRefPtr const top_value { top->value }; + ValueComparingNonnullRefPtr const right_value { right->value }; + ValueComparingNonnullRefPtr const bottom_value { bottom->value }; + ValueComparingNonnullRefPtr const left_value { left->value }; + + bool const top_and_bottom_same = top_value == bottom_value; + bool const left_and_right_same = left_value == right_value; + + RefPtr value; + + if (top_and_bottom_same && left_and_right_same && top_value == left_value) { + value = top_value; + } else if (top_and_bottom_same && left_and_right_same) { + value = StyleValueList::create(StyleValueVector { top_value, right_value }, StyleValueList::Separator::Space); + } else if (left_and_right_same) { + value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value }, StyleValueList::Separator::Space); + } else { + value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value, left_value }, StyleValueList::Separator::Space); + } + + return StyleProperty { + .important = top->important, + .property_id = property_id, + .value = value.release_nonnull(), + }; +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue +Optional CSSStyleProperties::get_property_internal(PropertyID property_id) const +{ + // 2. If property is a shorthand property, then follow these substeps: + if (property_is_shorthand(property_id)) { + + // AD-HOC: Handle shorthands that require manual construction. + switch (property_id) { + case PropertyID::Border: { + auto width = get_property_internal(PropertyID::BorderWidth); + auto style = get_property_internal(PropertyID::BorderStyle); + auto color = get_property_internal(PropertyID::BorderColor); + // `border` only has a reasonable value if all four sides are the same. + if (!width.has_value() || width->value->is_value_list() || !style.has_value() || style->value->is_value_list() || !color.has_value() || color->value->is_value_list()) + return {}; + if (width->important != style->important || width->important != color->important) + return {}; + return StyleProperty { + .important = width->important, + .property_id = property_id, + .value = ShorthandStyleValue::create(property_id, + { PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor }, + { width->value, style->value, color->value }) + }; + } + case PropertyID::BorderColor: { + auto top = get_property_internal(PropertyID::BorderTopColor); + auto right = get_property_internal(PropertyID::BorderRightColor); + auto bottom = get_property_internal(PropertyID::BorderBottomColor); + auto left = get_property_internal(PropertyID::BorderLeftColor); + return style_property_for_sided_shorthand(property_id, top, right, bottom, left); + } + case PropertyID::BorderStyle: { + auto top = get_property_internal(PropertyID::BorderTopStyle); + auto right = get_property_internal(PropertyID::BorderRightStyle); + auto bottom = get_property_internal(PropertyID::BorderBottomStyle); + auto left = get_property_internal(PropertyID::BorderLeftStyle); + return style_property_for_sided_shorthand(property_id, top, right, bottom, left); + } + case PropertyID::BorderWidth: { + auto top = get_property_internal(PropertyID::BorderTopWidth); + auto right = get_property_internal(PropertyID::BorderRightWidth); + auto bottom = get_property_internal(PropertyID::BorderBottomWidth); + auto left = get_property_internal(PropertyID::BorderLeftWidth); + return style_property_for_sided_shorthand(property_id, top, right, bottom, left); + } + case PropertyID::FontVariant: { + auto ligatures = get_property_internal(PropertyID::FontVariantLigatures); + auto caps = get_property_internal(PropertyID::FontVariantCaps); + auto alternates = get_property_internal(PropertyID::FontVariantAlternates); + auto numeric = get_property_internal(PropertyID::FontVariantNumeric); + auto east_asian = get_property_internal(PropertyID::FontVariantEastAsian); + auto position = get_property_internal(PropertyID::FontVariantPosition); + auto emoji = get_property_internal(PropertyID::FontVariantEmoji); + + if (!ligatures.has_value() || !caps.has_value() || !alternates.has_value() || !numeric.has_value() || !east_asian.has_value() || !position.has_value() || !emoji.has_value()) + return {}; + + if (ligatures->important != caps->important || ligatures->important != alternates->important || ligatures->important != numeric->important || ligatures->important != east_asian->important || ligatures->important != position->important || ligatures->important != emoji->important) + return {}; + + // If ligatures is `none` and any other value isn't `normal`, that's invalid. + if (ligatures->value->to_keyword() == Keyword::None + && (caps->value->to_keyword() != Keyword::Normal + || alternates->value->to_keyword() != Keyword::Normal + || numeric->value->to_keyword() != Keyword::Normal + || east_asian->value->to_keyword() != Keyword::Normal + || position->value->to_keyword() != Keyword::Normal + || emoji->value->to_keyword() != Keyword::Normal)) { + return {}; + } + + return StyleProperty { + .important = ligatures->important, + .property_id = property_id, + .value = ShorthandStyleValue::create(property_id, + { PropertyID::FontVariantLigatures, PropertyID::FontVariantCaps, PropertyID::FontVariantAlternates, PropertyID::FontVariantNumeric, PropertyID::FontVariantEastAsian, PropertyID::FontVariantPosition, PropertyID::FontVariantEmoji }, + { ligatures->value, caps->value, alternates->value, numeric->value, east_asian->value, position->value, emoji->value }) + }; + } + case PropertyID::Margin: { + auto top = get_property_internal(PropertyID::MarginTop); + auto right = get_property_internal(PropertyID::MarginRight); + auto bottom = get_property_internal(PropertyID::MarginBottom); + auto left = get_property_internal(PropertyID::MarginLeft); + return style_property_for_sided_shorthand(property_id, top, right, bottom, left); + } + case PropertyID::Padding: { + auto top = get_property_internal(PropertyID::PaddingTop); + auto right = get_property_internal(PropertyID::PaddingRight); + auto bottom = get_property_internal(PropertyID::PaddingBottom); + auto left = get_property_internal(PropertyID::PaddingLeft); + return style_property_for_sided_shorthand(property_id, top, right, bottom, left); + } + default: + break; + } + + // 1. Let list be a new empty array. + Vector> list; + Optional last_important_flag; + + // 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps: + Vector longhand_ids = longhands_for_shorthand(property_id); + for (auto longhand_property_id : longhand_ids) { + // 1. If longhand is a case-sensitive match for a property name of a CSS declaration in the declarations, + // let declaration be that CSS declaration, or null otherwise. + auto declaration = get_property_internal(longhand_property_id); + + // 2. If declaration is null, then return the empty string. + if (!declaration.has_value()) + return {}; + + // 3. Append the declaration to list. + list.append(declaration->value); + + if (last_important_flag.has_value() && declaration->important != *last_important_flag) + return {}; + last_important_flag = declaration->important; + } + + // 3. If important flags of all declarations in list are same, then return the serialization of list. + // NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string(). + return StyleProperty { + .important = last_important_flag.value(), + .property_id = property_id, + .value = ShorthandStyleValue::create(property_id, longhand_ids, list), + }; + + // 4. Return the empty string. + // NOTE: This is handled by the loop. + } + + return property(property_id); +} + RefPtr CSSStyleProperties::style_value_for_computed_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const { auto used_value_for_property = [&layout_node, property_id](Function&& used_value_getter) -> Optional { @@ -806,6 +1024,25 @@ WebIDL::ExceptionOr CSSStyleProperties::remove_property(StringView prope return value; } +WebIDL::ExceptionOr CSSStyleProperties::remove_property(PropertyID property_name) +{ + return remove_property(string_from_property_id(property_name)); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat +String CSSStyleProperties::css_float() const +{ + // The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument. + return get_property_value("float"sv); +} + +WebIDL::ExceptionOr CSSStyleProperties::set_css_float(StringView value) +{ + // On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value, + // and no third argument. Any exceptions thrown must be re-thrown. + return set_property("float"sv, value, ""sv); +} + // https://www.w3.org/TR/cssom/#serialize-a-css-declaration static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) { diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.h b/Libraries/LibWeb/CSS/CSSStyleProperties.h index 03fff5dbe64..a6405248f4a 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.h +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.h @@ -8,11 +8,14 @@ #pragma once #include +#include namespace Web::CSS { // https://drafts.csswg.org/cssom/#cssstyleproperties -class CSSStyleProperties : public CSSStyleDeclaration { +class CSSStyleProperties + : public CSSStyleDeclaration + , public Bindings::GeneratedCSSStyleProperties { WEB_PLATFORM_OBJECT(CSSStyleProperties, CSSStyleDeclaration); GC_DECLARE_ALLOCATOR(CSSStyleProperties); @@ -28,30 +31,41 @@ public: virtual size_t length() const override; virtual String item(size_t index) const override; - virtual Optional property(PropertyID) const override; - virtual Optional custom_property(FlyString const& custom_property_name) const override; + Optional property(PropertyID) const; + Optional custom_property(FlyString const& custom_property_name) const; - // Temporary for one commit. - using Base::remove_property, Base::set_property; + WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority = ""sv); + WebIDL::ExceptionOr remove_property(PropertyID); virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; + + virtual String get_property_value(StringView property_name) const override; + virtual StringView get_property_priority(StringView property_name) const override; + Vector const& properties() const { return m_properties; } HashMap const& custom_properties() const { return m_custom_properties; } size_t custom_property_count() const { return m_custom_properties.size(); } + String css_float() const; + WebIDL::ExceptionOr set_css_float(StringView); + virtual String serialized() const final override; virtual WebIDL::ExceptionOr set_css_text(StringView) override; void set_declarations_from_text(StringView); + // ^Bindings::GeneratedCSSStyleProperties + virtual CSSStyleProperties& generated_style_properties_to_css_style_properties() override { return *this; } + private: CSSStyleProperties(JS::Realm&, Computed, Readonly, Vector properties, HashMap custom_properties, Optional); virtual void visit_edges(Cell::Visitor&) override; RefPtr style_value_for_computed_property(Layout::NodeWithStyle const&, PropertyID) const; + Optional get_property_internal(PropertyID) const; bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); void empty_the_declarations(); diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.idl b/Libraries/LibWeb/CSS/CSSStyleProperties.idl index 22db5036419..bec81f01b60 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.idl +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.idl @@ -1,6 +1,10 @@ #import +#import // https://drafts.csswg.org/cssom/#cssstyleproperties [Exposed=Window] interface CSSStyleProperties : CSSStyleDeclaration { + [CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat; }; + +CSSStyleProperties includes GeneratedCSSStyleProperties; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp index ed138120da4..1d162d9f6ef 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp @@ -97,8 +97,8 @@ protected: GeneratedCSSStyleProperties() = default; virtual ~GeneratedCSSStyleProperties() = default; - virtual CSS::CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() = 0; - CSS::CSSStyleDeclaration const& generated_style_properties_to_css_style_declaration() const { return const_cast(*this).generated_style_properties_to_css_style_declaration(); } + virtual CSS::CSSStyleProperties& generated_style_properties_to_css_style_properties() = 0; + CSS::CSSStyleProperties const& generated_style_properties_to_css_style_properties() const { return const_cast(*this).generated_style_properties_to_css_style_properties(); } }; // class GeneratedCSSStyleProperties } // namespace Web::Bindings @@ -114,7 +114,7 @@ ErrorOr generate_implementation_file(JsonObject& properties, Core::File& f SourceGenerator generator { builder }; generator.append(R"~~~( -#include +#include #include #include @@ -131,12 +131,12 @@ namespace Web::Bindings { definition_generator.append(R"~~~( WebIDL::ExceptionOr GeneratedCSSStyleProperties::set_@name:acceptable_cpp@(StringView value) { - return generated_style_properties_to_css_style_declaration().set_property("@name@"sv, value, ""sv); + return generated_style_properties_to_css_style_properties().set_property("@name@"sv, value, ""sv); } String GeneratedCSSStyleProperties::@name:acceptable_cpp@() const { - return generated_style_properties_to_css_style_declaration().get_property_value("@name@"sv); + return generated_style_properties_to_css_style_properties().get_property_value("@name@"sv); } )~~~"); }); diff --git a/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt b/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt index cbd504d0973..fc942978953 100644 --- a/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt +++ b/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt @@ -1,3 +1,3 @@ -[object CSSStyleDeclaration] +[object CSSStyleProperties] [object DOMRectList] PASS (didn't crash) diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt similarity index 97% rename from Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt rename to Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt index aea43775afe..3245c3c0d4a 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt @@ -1,7 +1,4 @@ -All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle: -'cssText': '' -'length': '232' -'parentRule': 'null' +All supported properties and their default values exposed from CSSStyleProperties from getComputedStyle: 'cssFloat': 'none' 'WebkitAlignContent': 'normal' 'webkitAlignContent': 'normal' @@ -641,9 +638,4 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'y': '0px' 'zIndex': 'auto' 'z-index': 'auto' -'getPropertyPriority': 'function getPropertyPriority() { [native code] }' -'getPropertyValue': 'function getPropertyValue() { [native code] }' -'removeProperty': 'function removeProperty() { [native code] }' -'item': 'function item() { [native code] }' -'setProperty': 'function setProperty() { [native code] }' -'constructor': 'function CSSStyleDeclaration() { [native code] }' +'constructor': 'function CSSStyleProperties() { [native code] }' diff --git a/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt b/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt index 1d118bd8862..5b22506b0f2 100644 --- a/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt +++ b/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt @@ -1,4 +1,4 @@ spanRule: [object CSSStyleRule] ~ span { color: purple; } -spanRule.style: [object CSSStyleDeclaration] ~ span { color: purple; } +spanRule.style: [object CSSStyleProperties] ~ span { color: purple; } spanRule.style.parentRule: [object CSSStyleRule] ~ span { color: purple; } spanRule.style.parentRule === spanRule: true diff --git a/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-all-supported-properties-and-default-values.html b/Tests/LibWeb/Text/input/css/CSSStyleProperties-all-supported-properties-and-default-values.html similarity index 91% rename from Tests/LibWeb/Text/input/css/CSSStyleDeclaration-all-supported-properties-and-default-values.html rename to Tests/LibWeb/Text/input/css/CSSStyleProperties-all-supported-properties-and-default-values.html index a499a8c48ea..69af7250837 100644 --- a/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-all-supported-properties-and-default-values.html +++ b/Tests/LibWeb/Text/input/css/CSSStyleProperties-all-supported-properties-and-default-values.html @@ -10,7 +10,7 @@ const stylePrototype = Object.getPrototypeOf(defaultStyle); const supportedProperties = Object.getOwnPropertyNames(stylePrototype); - println("All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:"); + println("All supported properties and their default values exposed from CSSStyleProperties from getComputedStyle:"); for (const supportedProperty of supportedProperties) { println(`'${supportedProperty}': '${defaultStyle[supportedProperty]}'`); } From 9b06f66571ec808c930829a443e3d5215f3dfffa Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 13:41:46 +0000 Subject: [PATCH 079/141] LibWeb/CSS: Return GC::Ref from Parser::convert_to_style_declaration() --- Libraries/LibWeb/CSS/Parser/MediaParsing.cpp | 7 +---- Libraries/LibWeb/CSS/Parser/Parser.cpp | 2 +- Libraries/LibWeb/CSS/Parser/Parser.h | 2 +- Libraries/LibWeb/CSS/Parser/RuleParsing.cpp | 27 +++----------------- 4 files changed, 7 insertions(+), 31 deletions(-) diff --git a/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp b/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp index 579e83ad564..fcadb1a4864 100644 --- a/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp @@ -505,12 +505,7 @@ GC::Ptr Parser::convert_to_media_rule(AtRule const& rule, Nested n child_rules.append(child_rule); }, [&](Vector const& declarations) { - auto* declaration = convert_to_style_declaration(declarations); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding."); - return; - } - child_rules.append(CSSNestedDeclarations::create(realm(), *declaration)); + child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); }); } auto rule_list = CSSRuleList::create(realm(), child_rules); diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 3e4d4c00a6e..f54ed099c31 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -1493,7 +1493,7 @@ void Parser::extract_property(Declaration const& declaration, PropertiesAndCusto } } -CSSStyleProperties* Parser::convert_to_style_declaration(Vector const& declarations) +GC::Ref Parser::convert_to_style_declaration(Vector const& declarations) { PropertiesAndCustomProperties properties; PropertiesAndCustomProperties& dest = properties; diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 7c74ac7a938..2c7a350ead3 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -245,7 +245,7 @@ private: GC::Ptr convert_to_supports_rule(AtRule const&, Nested); GC::Ptr convert_to_property_rule(AtRule const& rule); - CSSStyleProperties* convert_to_style_declaration(Vector const&); + GC::Ref convert_to_style_declaration(Vector const&); Optional convert_to_style_property(Declaration const&); Optional parse_dimension(ComponentValue const&); diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index 883a1bde1d4..b4be2aac584 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -103,11 +103,7 @@ GC::Ptr Parser::convert_to_style_rule(QualifiedRule const& qualifi if (nested == Nested::Yes) selectors = adapt_nested_relative_selector_list(selectors); - auto* declaration = convert_to_style_declaration(qualified_rule.declarations); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding."); - return {}; - } + auto declaration = convert_to_style_declaration(qualified_rule.declarations); GC::RootVector child_rules { realm().heap() }; for (auto& child : qualified_rule.child_rules) { @@ -125,12 +121,7 @@ GC::Ptr Parser::convert_to_style_rule(QualifiedRule const& qualifi } }, [&](Vector const& declarations) { - auto* declaration = convert_to_style_declaration(declarations); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding."); - return; - } - child_rules.append(CSSNestedDeclarations::create(realm(), *declaration)); + child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); }); } auto nested_rules = CSSRuleList::create(realm(), move(child_rules)); @@ -256,12 +247,7 @@ GC::Ptr Parser::convert_to_layer_rule(AtRule const& rule, Nested nested child_rules.append(child_rule); }, [&](Vector const& declarations) { - auto* declaration = convert_to_style_declaration(declarations); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding."); - return; - } - child_rules.append(CSSNestedDeclarations::create(realm(), *declaration)); + child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); }); } auto rule_list = CSSRuleList::create(realm(), child_rules); @@ -478,12 +464,7 @@ GC::Ptr Parser::convert_to_supports_rule(AtRule const& rule, Ne child_rules.append(child_rule); }, [&](Vector const& declarations) { - auto* declaration = convert_to_style_declaration(declarations); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding."); - return; - } - child_rules.append(CSSNestedDeclarations::create(realm(), *declaration)); + child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); }); } From 2d220a8bbc91240324a1a1e1c68f2a9b0760fafa Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 13:52:01 +0000 Subject: [PATCH 080/141] LibWeb: Return CSSStyleProperties as a GC::Ref --- Libraries/LibWeb/CSS/CSSStyleRule.cpp | 2 +- Libraries/LibWeb/CSS/CSSStyleRule.h | 2 +- Libraries/LibWeb/DOM/Element.cpp | 4 ++-- Libraries/LibWeb/DOM/Element.h | 2 +- Libraries/LibWeb/Editing/Internal/Algorithms.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.cpp b/Libraries/LibWeb/CSS/CSSStyleRule.cpp index 010953d791c..3c42b5d1558 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleRule.cpp @@ -43,7 +43,7 @@ void CSSStyleRule::visit_edges(Cell::Visitor& visitor) } // https://drafts.csswg.org/cssom-1/#dom-cssstylerule-style -CSSStyleProperties* CSSStyleRule::style() +GC::Ref CSSStyleRule::style() { return m_declaration; } diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.h b/Libraries/LibWeb/CSS/CSSStyleRule.h index 572be8bd494..d1f14185f2c 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.h +++ b/Libraries/LibWeb/CSS/CSSStyleRule.h @@ -30,7 +30,7 @@ public: String selector_text() const; void set_selector_text(StringView); - CSSStyleProperties* style(); + GC::Ref style(); [[nodiscard]] FlyString const& qualified_layer_name() const { return parent_layer_internal_qualified_name(); } diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 07ed6c07b7e..d8a50e62e12 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -918,11 +918,11 @@ void Element::set_shadow_root(GC::Ptr shadow_root) invalidate_style(StyleInvalidationReason::ElementSetShadowRoot); } -CSS::CSSStyleProperties* Element::style_for_bindings() +GC::Ref Element::style_for_bindings() { if (!m_inline_style) m_inline_style = CSS::CSSStyleProperties::create_element_inline_style({ *this }, {}, {}); - return m_inline_style; + return *m_inline_style; } // https://dom.spec.whatwg.org/#element-html-uppercased-qualified-name diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index da314eda2e3..bab7dd93e25 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -214,7 +214,7 @@ public: GC::Ptr inline_style() { return m_inline_style; } GC::Ptr inline_style() const { return m_inline_style; } - CSS::CSSStyleProperties* style_for_bindings(); + GC::Ref style_for_bindings(); CSS::StyleSheetList& document_or_shadow_root_style_sheets(); diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index f5680fbb69c..0e039317991 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -2669,7 +2669,7 @@ void justify_the_selection(DOM::Document& document, JustifyAlignment alignment) element->remove_attribute_ns(Namespace::HTML, HTML::AttributeNames::align); // 2. Unset the CSS property "text-align" on element, if it's set by a style attribute. - auto* inline_style = element->style_for_bindings(); + auto inline_style = element->style_for_bindings(); MUST(inline_style->remove_property(CSS::PropertyID::TextAlign)); // 3. If element is a div or span or center with no attributes, remove it, preserving its descendants. From 9dcbf5562aeb49327b26468a279450f3091a330f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 16 Mar 2025 09:32:07 -0400 Subject: [PATCH 081/141] LibWeb: Extract some Internals functionality to a base class This will just allow re-using these definitions on other internal pages. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/Internals/Internals.cpp | 52 ++++++++------------ Libraries/LibWeb/Internals/Internals.h | 10 ++-- Libraries/LibWeb/Internals/InternalsBase.cpp | 33 +++++++++++++ Libraries/LibWeb/Internals/InternalsBase.h | 28 +++++++++++ 5 files changed, 87 insertions(+), 37 deletions(-) create mode 100644 Libraries/LibWeb/Internals/InternalsBase.cpp create mode 100644 Libraries/LibWeb/Internals/InternalsBase.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 75b29cce574..6d39459ed23 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -562,6 +562,7 @@ set(SOURCES IndexedDB/Internal/RequestList.cpp Internals/InternalAnimationTimeline.cpp Internals/Internals.cpp + Internals/InternalsBase.cpp IntersectionObserver/IntersectionObserver.cpp IntersectionObserver/IntersectionObserverEntry.cpp Layout/AudioBox.cpp diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 797ff8bf483..d0f0fe46eb5 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -26,7 +26,7 @@ static u16 s_echo_server_port { 0 }; GC_DEFINE_ALLOCATOR(Internals); Internals::Internals(JS::Realm& realm) - : Bindings::PlatformObject(realm) + : InternalsBase(realm) { } @@ -38,24 +38,14 @@ void Internals::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(Internals); } -HTML::Window& Internals::internals_window() const -{ - return as(HTML::relevant_global_object(*this)); -} - -Page& Internals::internals_page() const -{ - return internals_window().page(); -} - void Internals::signal_test_is_done(String const& text) { - internals_page().client().page_did_finish_test(text); + page().client().page_did_finish_test(text); } void Internals::set_test_timeout(double milliseconds) { - internals_page().client().page_did_set_test_timeout(milliseconds); + page().client().page_did_set_test_timeout(milliseconds); } void Internals::gc() @@ -65,7 +55,7 @@ void Internals::gc() JS::Object* Internals::hit_test(double x, double y) { - auto& active_document = internals_window().associated_document(); + auto& active_document = window().associated_document(); // NOTE: Force a layout update just before hit testing. This is because the current layout tree, which is required // for stacking context traversal, might not exist if this call occurs between the tear_down_layout_tree() // and update_layout() calls @@ -82,7 +72,7 @@ JS::Object* Internals::hit_test(double x, double y) void Internals::send_text(HTML::HTMLElement& target, String const& text, WebIDL::UnsignedShort modifiers) { - auto& page = internals_page(); + auto& page = this->page(); target.focus(); for (auto code_point : text.code_points()) @@ -94,12 +84,12 @@ void Internals::send_key(HTML::HTMLElement& target, String const& key_name, WebI auto key_code = UIEvents::key_code_from_string(key_name); target.focus(); - internals_page().handle_keydown(key_code, modifiers, 0, false); + page().handle_keydown(key_code, modifiers, 0, false); } void Internals::commit_text() { - internals_page().handle_keydown(UIEvents::Key_Return, 0, 0, false); + page().handle_keydown(UIEvents::Key_Return, 0, 0, false); } void Internals::click(double x, double y) @@ -109,7 +99,7 @@ void Internals::click(double x, double y) void Internals::doubleclick(double x, double y) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_doubleclick(position, position, UIEvents::MouseButton::Primary, 0, 0); @@ -122,7 +112,7 @@ void Internals::middle_click(double x, double y) void Internals::click(double x, double y, UIEvents::MouseButton button) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_mousedown(position, position, button, 0, 0); @@ -136,14 +126,14 @@ void Internals::mouse_down(double x, double y) void Internals::mouse_down(double x, double y, UIEvents::MouseButton button) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_mousedown(position, position, button, 0, 0); } void Internals::move_pointer_to(double x, double y) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_mousemove(position, position, 0, 0); @@ -151,7 +141,7 @@ void Internals::move_pointer_to(double x, double y) void Internals::wheel(double x, double y, double delta_x, double delta_y) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_mousewheel(position, position, 0, 0, 0, delta_x, delta_y); @@ -171,7 +161,7 @@ void Internals::spoof_current_url(String const& url_string) auto origin = url->origin(); - auto& window = internals_window(); + auto& window = this->window(); window.associated_document().set_url(url.value()); window.associated_document().set_origin(origin); HTML::relevant_settings_object(window.associated_document()).creation_url = url.release_value(); @@ -188,7 +178,7 @@ void Internals::simulate_drag_start(double x, double y, String const& name, Stri Vector files; files.empend(name.to_byte_string(), MUST(ByteBuffer::copy(contents.bytes()))); - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_drag_and_drop_event(DragEvent::Type::DragStart, position, position, UIEvents::MouseButton::Primary, 0, 0, move(files)); @@ -196,7 +186,7 @@ void Internals::simulate_drag_start(double x, double y, String const& name, Stri void Internals::simulate_drag_move(double x, double y) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_drag_and_drop_event(DragEvent::Type::DragMove, position, position, UIEvents::MouseButton::Primary, 0, 0, {}); @@ -204,7 +194,7 @@ void Internals::simulate_drag_move(double x, double y) void Internals::simulate_drop(double x, double y) { - auto& page = internals_page(); + auto& page = this->page(); auto position = page.css_to_device_point({ x, y }); page.handle_drag_and_drop_event(DragEvent::Type::Drop, position, position, UIEvents::MouseButton::Primary, 0, 0, {}); @@ -212,12 +202,12 @@ void Internals::simulate_drop(double x, double y) void Internals::enable_cookies_on_file_domains() { - internals_window().associated_document().enable_cookies_on_file_domains({}); + window().associated_document().enable_cookies_on_file_domains({}); } void Internals::expire_cookies_with_time_offset(WebIDL::LongLong seconds) { - internals_page().client().page_did_expire_cookies_with_time_offset(AK::Duration::from_seconds(seconds)); + page().client().page_did_expire_cookies_with_time_offset(AK::Duration::from_seconds(seconds)); } // NOLINTNEXTLINE(readability-convert-member-functions-to-static @@ -230,7 +220,7 @@ String Internals::get_computed_role(DOM::Element& element) String Internals::get_computed_label(DOM::Element& element) { - auto& active_document = internals_window().associated_document(); + auto& active_document = window().associated_document(); return MUST(element.accessible_name(active_document)); } @@ -246,12 +236,12 @@ void Internals::set_echo_server_port(u16 const port) void Internals::set_browser_zoom(double factor) { - internals_page().client().page_did_set_browser_zoom(factor); + page().client().page_did_set_browser_zoom(factor); } bool Internals::headless() { - return internals_page().client().is_headless(); + return page().client().is_headless(); } } diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index f7872be8730..070053d6162 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -6,15 +6,15 @@ #pragma once -#include #include +#include #include #include namespace Web::Internals { -class Internals final : public Bindings::PlatformObject { - WEB_PLATFORM_OBJECT(Internals, Bindings::PlatformObject); +class Internals final : public InternalsBase { + WEB_PLATFORM_OBJECT(Internals, InternalsBase); GC_DECLARE_ALLOCATOR(Internals); public: @@ -62,13 +62,11 @@ public: private: explicit Internals(JS::Realm&); + virtual void initialize(JS::Realm&) override; void click(double x, double y, UIEvents::MouseButton); void mouse_down(double x, double y, UIEvents::MouseButton); - - HTML::Window& internals_window() const; - Page& internals_page() const; }; } diff --git a/Libraries/LibWeb/Internals/InternalsBase.cpp b/Libraries/LibWeb/Internals/InternalsBase.cpp new file mode 100644 index 00000000000..5a7045ce0ab --- /dev/null +++ b/Libraries/LibWeb/Internals/InternalsBase.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::Internals { + +GC_DEFINE_ALLOCATOR(InternalsBase); + +InternalsBase::InternalsBase(JS::Realm& realm) + : Bindings::PlatformObject(realm) +{ +} + +InternalsBase::~InternalsBase() = default; + +HTML::Window& InternalsBase::window() const +{ + return as(HTML::relevant_global_object(*this)); +} + +Page& InternalsBase::page() const +{ + return window().page(); +} + +} diff --git a/Libraries/LibWeb/Internals/InternalsBase.h b/Libraries/LibWeb/Internals/InternalsBase.h new file mode 100644 index 00000000000..192b9ed7b8e --- /dev/null +++ b/Libraries/LibWeb/Internals/InternalsBase.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Internals { + +class InternalsBase : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(InternalsBase, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(InternalsBase); + +public: + virtual ~InternalsBase() override; + +protected: + explicit InternalsBase(JS::Realm&); + + HTML::Window& window() const; + Page& page() const; +}; + +} From 843209c6a961bad53aa58bc20a3135954914b3f7 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 16 Mar 2025 10:49:28 -0400 Subject: [PATCH 082/141] LibWeb+LibWebView+WebContent: Add an about:processes page The intent is that this will replace the separate Task Manager window. This will allow us to more easily add features such as actual process management, better rendering of the process table, etc. Included in this page is the ability to sort table rows. This also lays the ground work for more internal `about` pages, such as about:config. --- Base/res/ladybird/about-pages/processes.html | 175 ++++++++++++++++++ Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/Forward.h | 1 + .../WindowEnvironmentSettingsObject.cpp | 2 +- Libraries/LibWeb/HTML/Window.cpp | 11 +- Libraries/LibWeb/HTML/Window.h | 2 +- Libraries/LibWeb/Internals/Processes.cpp | 35 ++++ Libraries/LibWeb/Internals/Processes.h | 28 +++ Libraries/LibWeb/Internals/Processes.idl | 4 + Libraries/LibWeb/Page/Page.h | 2 + Libraries/LibWeb/idl_files.cmake | 1 + Libraries/LibWebView/Application.cpp | 13 ++ Libraries/LibWebView/Application.h | 2 + Libraries/LibWebView/ProcessManager.cpp | 31 ++++ Libraries/LibWebView/ProcessManager.h | 1 + Libraries/LibWebView/WebContentClient.cpp | 6 + Libraries/LibWebView/WebContentClient.h | 1 + Services/WebContent/PageClient.cpp | 5 + Services/WebContent/PageClient.h | 1 + Services/WebContent/WebContentClient.ipc | 2 + UI/cmake/ResourceFiles.cmake | 1 + 21 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 Base/res/ladybird/about-pages/processes.html create mode 100644 Libraries/LibWeb/Internals/Processes.cpp create mode 100644 Libraries/LibWeb/Internals/Processes.h create mode 100644 Libraries/LibWeb/Internals/Processes.idl diff --git a/Base/res/ladybird/about-pages/processes.html b/Base/res/ladybird/about-pages/processes.html new file mode 100644 index 00000000000..6d630831e1f --- /dev/null +++ b/Base/res/ladybird/about-pages/processes.html @@ -0,0 +1,175 @@ + + + + Task Manager + + + + + + + + + + + + + +
NamePIDCPUMemory
+ + + + diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 6d39459ed23..db826f8432e 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -563,6 +563,7 @@ set(SOURCES Internals/InternalAnimationTimeline.cpp Internals/Internals.cpp Internals/InternalsBase.cpp + Internals/Processes.cpp IntersectionObserver/IntersectionObserver.cpp IntersectionObserver/IntersectionObserverEntry.cpp Layout/AudioBox.cpp diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index a0b40c9e817..d85324a960c 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -624,6 +624,7 @@ class RequestList; namespace Web::Internals { class Internals; +class Processes; } namespace Web::IntersectionObserver { diff --git a/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp b/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp index be50aff2f83..d17003e13b4 100644 --- a/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp +++ b/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp @@ -79,7 +79,7 @@ void WindowEnvironmentSettingsObject::setup(Page& page, URL::URL const& creation // Non-Standard: We cannot fully initialize window object until *after* the we set up // the realm's [[HostDefined]] internal slot as the internal slot contains the web platform intrinsics - MUST(window.initialize_web_interfaces({})); + MUST(window.initialize_web_interfaces({}, creation_url)); } // https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:responsible-document diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 3fae3a9bee3..ba3b173f280 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -721,7 +722,7 @@ void Window::set_internals_object_exposed(bool exposed) s_internals_object_exposed = exposed; } -WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge) +WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge, URL::URL const& url) { auto& realm = this->realm(); add_window_exposed_interfaces(*this); @@ -734,6 +735,14 @@ WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge(realm), JS::default_attributes); + if (url.scheme() == "about"sv && url.paths().size() == 1) { + auto const& path = url.paths().first(); + + if (path == "processes"sv) { + define_direct_property("processes", realm.create(realm), JS::default_attributes); + } + } + return {}; } diff --git a/Libraries/LibWeb/HTML/Window.h b/Libraries/LibWeb/HTML/Window.h index 4a8645973c0..899d07011ee 100644 --- a/Libraries/LibWeb/HTML/Window.h +++ b/Libraries/LibWeb/HTML/Window.h @@ -148,7 +148,7 @@ public: // https://html.spec.whatwg.org/multipage/interaction.html#history-action-activation bool has_history_action_activation() const; - WebIDL::ExceptionOr initialize_web_interfaces(Badge); + WebIDL::ExceptionOr initialize_web_interfaces(Badge, URL::URL const&); Vector> pdf_viewer_plugin_objects(); Vector> pdf_viewer_mime_type_objects(); diff --git a/Libraries/LibWeb/Internals/Processes.cpp b/Libraries/LibWeb/Internals/Processes.cpp new file mode 100644 index 00000000000..09cd2d71c65 --- /dev/null +++ b/Libraries/LibWeb/Internals/Processes.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::Internals { + +GC_DEFINE_ALLOCATOR(Processes); + +Processes::Processes(JS::Realm& realm) + : InternalsBase(realm) +{ +} + +Processes::~Processes() = default; + +void Processes::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(Processes); +} + +void Processes::update_process_statistics() +{ + page().client().update_process_statistics(); +} + +} diff --git a/Libraries/LibWeb/Internals/Processes.h b/Libraries/LibWeb/Internals/Processes.h new file mode 100644 index 00000000000..2fcbea7b0bb --- /dev/null +++ b/Libraries/LibWeb/Internals/Processes.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Internals { + +class Processes final : public InternalsBase { + WEB_PLATFORM_OBJECT(Processes, InternalsBase); + GC_DECLARE_ALLOCATOR(Processes); + +public: + virtual ~Processes() override; + + void update_process_statistics(); + +private: + explicit Processes(JS::Realm&); + + virtual void initialize(JS::Realm&) override; +}; + +} diff --git a/Libraries/LibWeb/Internals/Processes.idl b/Libraries/LibWeb/Internals/Processes.idl new file mode 100644 index 00000000000..9be603113f2 --- /dev/null +++ b/Libraries/LibWeb/Internals/Processes.idl @@ -0,0 +1,4 @@ +[Exposed=Nobody] +interface Processes { + undefined updateProcessStatistics(); +}; diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index ef834ada7c7..5abb1043cf5 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -400,6 +400,8 @@ public: virtual void page_did_mutate_dom([[maybe_unused]] FlyString const& type, [[maybe_unused]] DOM::Node const& target, [[maybe_unused]] DOM::NodeList& added_nodes, [[maybe_unused]] DOM::NodeList& removed_nodes, [[maybe_unused]] GC::Ptr previous_sibling, [[maybe_unused]] GC::Ptr next_sibling, [[maybe_unused]] Optional const& attribute_name) { } + virtual void update_process_statistics() { } + virtual bool is_ready_to_paint() const = 0; virtual DisplayListPlayerType display_list_player_type() const = 0; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 1ca79bc8591..b51b98cd335 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -266,6 +266,7 @@ libweb_js_bindings(IndexedDB/IDBTransaction) libweb_js_bindings(IndexedDB/IDBVersionChangeEvent) libweb_js_bindings(Internals/InternalAnimationTimeline) libweb_js_bindings(Internals/Internals) +libweb_js_bindings(Internals/Processes) libweb_js_bindings(IntersectionObserver/IntersectionObserver) libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry) libweb_js_bindings(MathML/MathMLElement) diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 94ffe6521ff..faed92da45b 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -331,6 +331,19 @@ String Application::generate_process_statistics_html() return m_process_manager.generate_html(); } +void Application::send_updated_process_statistics_to_view(ViewImplementation& view) +{ + m_process_manager.update_all_process_statistics(); + auto statistics = m_process_manager.serialize_json(); + + StringBuilder builder; + builder.append("processes.loadProcessStatistics(\""sv); + builder.append_escaped_for_json(statistics); + builder.append("\");"sv); + + view.run_javascript(MUST(builder.to_string())); +} + void Application::process_did_exit(Process&& process) { if (m_in_shutdown) diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index ce5becec20d..4d46b1d6a80 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -60,6 +60,8 @@ public: void update_process_statistics(); String generate_process_statistics_html(); + void send_updated_process_statistics_to_view(ViewImplementation&); + ErrorOr path_for_downloaded_file(StringView file) const; enum class DevtoolsState { diff --git a/Libraries/LibWebView/ProcessManager.cpp b/Libraries/LibWebView/ProcessManager.cpp index 281be7a2647..e3a0352baf3 100644 --- a/Libraries/LibWebView/ProcessManager.cpp +++ b/Libraries/LibWebView/ProcessManager.cpp @@ -4,6 +4,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include @@ -202,4 +204,33 @@ String ProcessManager::generate_html() return builder.to_string_without_validation(); } +String ProcessManager::serialize_json() +{ + Threading::MutexLocker locker { m_lock }; + + StringBuilder builder; + auto serializer = MUST(JsonArraySerializer<>::try_create(builder)); + + m_statistics.for_each_process([&](auto const& process) { + auto& process_handle = find_process(process.pid).value(); + + auto type = WebView::process_name_from_type(process_handle.type()); + auto const& title = process_handle.title(); + + auto process_name = title.has_value() + ? MUST(String::formatted("{} - {}", type, *title)) + : String::from_utf8_without_validation(type.bytes()); + + auto object = MUST(serializer.add_object()); + MUST(object.add("name"sv, move(process_name))); + MUST(object.add("pid"sv, process.pid)); + MUST(object.add("cpu"sv, process.cpu_percent)); + MUST(object.add("memory"sv, process.memory_usage_bytes)); + MUST(object.finish()); + }); + + MUST(serializer.finish()); + return MUST(builder.to_string()); +} + } diff --git a/Libraries/LibWebView/ProcessManager.h b/Libraries/LibWebView/ProcessManager.h index 21f5bf435fe..478b9f56341 100644 --- a/Libraries/LibWebView/ProcessManager.h +++ b/Libraries/LibWebView/ProcessManager.h @@ -37,6 +37,7 @@ public: void update_all_process_statistics(); String generate_html(); + String serialize_json(); Function on_process_exited; diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index 6d742706f48..462f382e9eb 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -690,6 +690,12 @@ Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request return IPC::File {}; } +void WebContentClient::update_process_statistics(u64 page_id) +{ + if (auto view = view_for_page_id(page_id); view.has_value()) + WebView::Application::the().send_updated_process_statistics_to_view(*view); +} + Optional WebContentClient::view_for_page_id(u64 page_id, SourceLocation location) { // Don't bother logging anything for the spare WebContent process. It will only receive a load notification for about:blank. diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index b2dde34bf44..d01b5148c27 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -129,6 +129,7 @@ private: virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override; virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap, i32 back_bitmap_id, Gfx::ShareableBitmap) override; virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id) override; + virtual void update_process_statistics(u64 page_id) override; Optional view_for_page_id(u64, SourceLocation = SourceLocation::current()); diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index ed3f59b7bfd..eed200cdd51 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -699,6 +699,11 @@ void PageClient::page_did_mutate_dom(FlyString const& type, Web::DOM::Node const client().async_did_mutate_dom(m_id, { type.to_string(), target.unique_id(), move(serialized_target), mutation.release_value() }); } +void PageClient::update_process_statistics() +{ + client().async_update_process_statistics(m_id); +} + ErrorOr PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path) { VERIFY(!m_webdriver); diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index 666feaa300c..b04811f4d27 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -174,6 +174,7 @@ private: virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) override; virtual IPC::File request_worker_agent() override; virtual void page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr previous_sibling, GC::Ptr next_sibling, Optional const& attribute_name) override; + virtual void update_process_statistics() override; Web::Layout::Viewport* layout_root(); void setup_palette(); diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index 732b3839d4f..b7b6d076b33 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -108,4 +108,6 @@ endpoint WebContentClient did_find_in_page(u64 page_id, size_t current_match_index, Optional total_match_count) =| request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent + + update_process_statistics(u64 page_id) =| } diff --git a/UI/cmake/ResourceFiles.cmake b/UI/cmake/ResourceFiles.cmake index 1447fe40223..a2f3ee8d694 100644 --- a/UI/cmake/ResourceFiles.cmake +++ b/UI/cmake/ResourceFiles.cmake @@ -64,6 +64,7 @@ list(TRANSFORM BROWSER_ICONS PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/icons/brow set(ABOUT_PAGES about.html newtab.html + processes.html ) set(WEB_TEMPLATES directory.html From 67a1dd72db5da70d0061338223266d87c70f6468 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 16 Mar 2025 10:58:05 -0400 Subject: [PATCH 083/141] UI/AppKit: Replace the Task Manager window with about:processes --- UI/AppKit/Application/ApplicationDelegate.mm | 36 ++-------- UI/AppKit/CMakeLists.txt | 19 ------ UI/AppKit/Interface/TaskManager.h | 18 ----- UI/AppKit/Interface/TaskManager.mm | 66 ------------------- UI/AppKit/Interface/TaskManager.swift | 46 ------------- UI/AppKit/Interface/TaskManagerController.h | 21 ------ UI/AppKit/Interface/TaskManagerController.mm | 65 ------------------ .../Interface/TaskManagerController.swift | 50 -------------- 8 files changed, 4 insertions(+), 317 deletions(-) delete mode 100644 UI/AppKit/Interface/TaskManager.h delete mode 100644 UI/AppKit/Interface/TaskManager.mm delete mode 100644 UI/AppKit/Interface/TaskManager.swift delete mode 100644 UI/AppKit/Interface/TaskManagerController.h delete mode 100644 UI/AppKit/Interface/TaskManagerController.mm delete mode 100644 UI/AppKit/Interface/TaskManagerController.swift diff --git a/UI/AppKit/Application/ApplicationDelegate.mm b/UI/AppKit/Application/ApplicationDelegate.mm index 3260bd0b34a..b8deadca7c6 100644 --- a/UI/AppKit/Application/ApplicationDelegate.mm +++ b/UI/AppKit/Application/ApplicationDelegate.mm @@ -15,22 +15,13 @@ #import #import -#if defined(LADYBIRD_USE_SWIFT) -// FIXME: Report this codegen error to Apple -# define StyleMask NSWindowStyleMask -# import -# undef StyleMask -#else -# import -#endif - #import #if !__has_feature(objc_arc) # error "This project requires ARC" #endif -@interface ApplicationDelegate () +@interface ApplicationDelegate () { Web::CSS::PreferredColorScheme m_preferred_color_scheme; Web::CSS::PreferredContrast m_preferred_contrast; @@ -45,8 +36,6 @@ @property (nonatomic, strong) InfoBar* info_bar; -@property (nonatomic, strong) TaskManagerController* task_manager_controller; - @property (nonatomic, strong) NSMenuItem* toggle_devtools_menu_item; - (NSMenuItem*)createApplicationMenu; @@ -152,12 +141,6 @@ - (void)removeTab:(TabController*)controller { [self.managed_tabs removeObject:controller]; - - if ([self.managed_tabs count] == 0u) { - if (self.task_manager_controller != nil) { - [self.task_manager_controller.window close]; - } - } } - (Web::CSS::PreferredColorScheme)preferredColorScheme @@ -299,13 +282,9 @@ - (void)openTaskManager:(id)sender { - if (self.task_manager_controller != nil) { - [self.task_manager_controller.window makeKeyAndOrderFront:sender]; - return; - } - - self.task_manager_controller = [[TaskManagerController alloc] initWithDelegate:self]; - [self.task_manager_controller showWindow:nil]; + [self createNewTab:URL::URL::about("processes"_string) + fromTab:self.active_tab + activateTab:Web::HTML::ActivateTab::Yes]; } - (void)openLocation:(id)sender @@ -864,11 +843,4 @@ return YES; } -#pragma mark - TaskManagerDelegate - -- (void)onTaskManagerClosed -{ - self.task_manager_controller = nil; -} - @end diff --git a/UI/AppKit/CMakeLists.txt b/UI/AppKit/CMakeLists.txt index b3d07cc40b1..1705e2f5fbb 100644 --- a/UI/AppKit/CMakeLists.txt +++ b/UI/AppKit/CMakeLists.txt @@ -21,25 +21,6 @@ target_compile_options(ladybird_impl PUBLIC ) target_compile_features(ladybird_impl PUBLIC cxx_std_23) -if (ENABLE_SWIFT) - target_sources(ladybird_impl PRIVATE - Interface/TaskManager.swift - Interface/TaskManagerController.swift - ) - target_compile_definitions(ladybird_impl PUBLIC LADYBIRD_USE_SWIFT) - set_target_properties(ladybird_impl PROPERTIES Swift_MODULE_NAME "SwiftLadybird") - - get_target_property(LADYBIRD_NATIVE_DIRS ladybird_impl INCLUDE_DIRECTORIES) - _swift_generate_cxx_header(ladybird_impl "Ladybird-Swift.h" - SEARCH_PATHS ${LADYBIRD_NATIVE_DIRS} - ) -else() - target_sources(ladybird_impl PRIVATE - Interface/TaskManager.mm - Interface/TaskManagerController.mm - ) -endif() - add_executable(ladybird MACOSX_BUNDLE main.mm ) diff --git a/UI/AppKit/Interface/TaskManager.h b/UI/AppKit/Interface/TaskManager.h deleted file mode 100644 index 1a7d0a0a273..00000000000 --- a/UI/AppKit/Interface/TaskManager.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#import -#import - -@class LadybirdWebView; - -@interface TaskManager : LadybirdWebViewWindow - -- (instancetype)init; - -@end diff --git a/UI/AppKit/Interface/TaskManager.mm b/UI/AppKit/Interface/TaskManager.mm deleted file mode 100644 index 9fd55645005..00000000000 --- a/UI/AppKit/Interface/TaskManager.mm +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -#import -#import - -#if !__has_feature(objc_arc) -# error "This project requires ARC" -#endif - -static constexpr CGFloat const WINDOW_WIDTH = 600; -static constexpr CGFloat const WINDOW_HEIGHT = 400; - -@interface TaskManager () -{ - RefPtr m_update_timer; -} - -@end - -@implementation TaskManager - -- (instancetype)init -{ - auto tab_rect = [[NSApp keyWindow] frame]; - auto position_x = tab_rect.origin.x + (tab_rect.size.width - WINDOW_WIDTH) / 2; - auto position_y = tab_rect.origin.y + (tab_rect.size.height - WINDOW_HEIGHT) / 2; - auto window_rect = NSMakeRect(position_x, position_y, WINDOW_WIDTH, WINDOW_HEIGHT); - - if (self = [super initWithWebView:nil windowRect:window_rect]) { - __weak TaskManager* weak_self = self; - - m_update_timer = Core::Timer::create_repeating(1000, [weak_self] { - TaskManager* strong_self = weak_self; - if (strong_self == nil) { - return; - } - - [strong_self updateStatistics]; - }); - - [self setContentView:self.web_view]; - [self setTitle:@"Task Manager"]; - [self setIsVisible:YES]; - - [self updateStatistics]; - m_update_timer->start(); - } - - return self; -} - -- (void)updateStatistics -{ - WebView::Application::the().update_process_statistics(); - [self.web_view loadHTML:WebView::Application::the().generate_process_statistics_html()]; -} - -@end diff --git a/UI/AppKit/Interface/TaskManager.swift b/UI/AppKit/Interface/TaskManager.swift deleted file mode 100644 index 92118fd513e..00000000000 --- a/UI/AppKit/Interface/TaskManager.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * Copyright (c) 2024, Andrew Kaster - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import Foundation -import Ladybird.WebView -import Ladybird.WebViewApplication -import Ladybird.WebViewWindow -import SwiftUI - -public class TaskManager: LadybirdWebViewWindow { - - private let WINDOW_WIDTH: CGFloat = 600 - private let WINDOW_HEIGHT: CGFloat = 400 - - private var timer: Timer? - - init() { - let tab_rect = NSApplication.shared.keyWindow!.frame - let position_x = tab_rect.origin.x + (tab_rect.size.width - WINDOW_WIDTH) / 2 - let position_y = tab_rect.origin.y + (tab_rect.size.height - WINDOW_HEIGHT) / 2 - let window_rect = NSMakeRect(position_x, position_y, WINDOW_WIDTH, WINDOW_HEIGHT) - - super.init(webView: nil, windowRect: window_rect) - - self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in - if let strong_self = self { - strong_self.updateStatistics() - } - } - - self.contentView = self.web_view - self.title = "Task Manager" - self.setIsVisible(true) - - self.updateStatistics() - } - - func updateStatistics() { - WebView.Application.the().update_process_statistics() - self.web_view.loadHTML(WebView.Application.the().generate_process_statistics_html().__bytes_as_string_viewUnsafe()) - } -} diff --git a/UI/AppKit/Interface/TaskManagerController.h b/UI/AppKit/Interface/TaskManagerController.h deleted file mode 100644 index 9875f21f8d0..00000000000 --- a/UI/AppKit/Interface/TaskManagerController.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#import - -@protocol TaskManagerDelegate - -- (void)onTaskManagerClosed; - -@end - -@interface TaskManagerController : NSWindowController - -- (instancetype)initWithDelegate:(id)delegate; - -@end diff --git a/UI/AppKit/Interface/TaskManagerController.mm b/UI/AppKit/Interface/TaskManagerController.mm deleted file mode 100644 index 0763a333a1b..00000000000 --- a/UI/AppKit/Interface/TaskManagerController.mm +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#import -#import -#import - -#if !__has_feature(objc_arc) -# error "This project requires ARC" -#endif - -@interface TaskManagerController () - -@property (nonatomic, weak) id delegate; - -@end - -@implementation TaskManagerController - -- (instancetype)initWithDelegate:(id)delegate -{ - if (self = [super init]) { - self.delegate = delegate; - } - - return self; -} - -#pragma mark - Private methods - -- (TaskManager*)taskManager -{ - return (TaskManager*)[self window]; -} - -#pragma mark - NSWindowController - -- (IBAction)showWindow:(id)sender -{ - self.window = [[TaskManager alloc] init]; - [self.window setDelegate:self]; - [self.window makeKeyAndOrderFront:sender]; -} - -#pragma mark - NSWindowDelegate - -- (void)windowWillClose:(NSNotification*)notification -{ - [self.delegate onTaskManagerClosed]; -} - -- (void)windowDidResize:(NSNotification*)notification -{ - [[[self taskManager] web_view] handleResize]; -} - -- (void)windowDidChangeBackingProperties:(NSNotification*)notification -{ - [[[self taskManager] web_view] handleDevicePixelRatioChange]; -} - -@end diff --git a/UI/AppKit/Interface/TaskManagerController.swift b/UI/AppKit/Interface/TaskManagerController.swift deleted file mode 100644 index 67ac5149175..00000000000 --- a/UI/AppKit/Interface/TaskManagerController.swift +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * Copyright (c) 2024, Andrew Kaster - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import Foundation -import SwiftUI - -@objc -public protocol TaskManagerDelegate where Self: NSObject { - func onTaskManagerClosed() -} - -public class TaskManagerController: NSWindowController, NSWindowDelegate { - - private weak var delegate: TaskManagerDelegate? - - @objc - public convenience init(delegate: TaskManagerDelegate) { - self.init() - self.delegate = delegate - } - - @IBAction public override func showWindow(_ sender: Any?) { - self.window = TaskManager.init() - self.window!.delegate = self - self.window!.makeKeyAndOrderFront(sender) - } - - public func windowWillClose(_ sender: Notification) { - self.delegate?.onTaskManagerClosed() - } - - public func windowDidResize(_ sender: Notification) { - guard self.window != nil else { return } - if !self.window!.inLiveResize { - self.taskManager().web_view.handleResize() - } - } - - public func windowDidChangeBackingProperties(_ sender: Notification) { - self.taskManager().web_view.handleDevicePixelRatioChange() - } - - private func taskManager() -> TaskManager { - return self.window as! TaskManager - } -} From 45d8cd5c9fe2b2b30629929d2c1b4027e615f96c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 16 Mar 2025 11:16:58 -0400 Subject: [PATCH 084/141] UI/Qt: Replace the Task Manager window with about:processes --- UI/Qt/Application.cpp | 25 +------------------ UI/Qt/Application.h | 4 --- UI/Qt/BrowserWindow.cpp | 5 ++-- UI/Qt/BrowserWindow.h | 1 - UI/Qt/CMakeLists.txt | 1 - UI/Qt/TaskManagerWindow.cpp | 49 ------------------------------------- UI/Qt/TaskManagerWindow.h | 32 ------------------------ 7 files changed, 3 insertions(+), 114 deletions(-) delete mode 100644 UI/Qt/TaskManagerWindow.cpp delete mode 100644 UI/Qt/TaskManagerWindow.h diff --git a/UI/Qt/Application.cpp b/UI/Qt/Application.cpp index 740c57551b9..3f1c57c40df 100644 --- a/UI/Qt/Application.cpp +++ b/UI/Qt/Application.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -26,10 +25,7 @@ void Application::create_platform_options(WebView::BrowserOptions&, WebView::Web web_content_options.config_path = Settings::the()->directory(); } -Application::~Application() -{ - close_task_manager_window(); -} +Application::~Application() = default; bool Application::event(QEvent* event) { @@ -52,25 +48,6 @@ bool Application::event(QEvent* event) return QApplication::event(event); } -void Application::show_task_manager_window() -{ - if (!m_task_manager_window) { - m_task_manager_window = new TaskManagerWindow(nullptr); - } - m_task_manager_window->show(); - m_task_manager_window->activateWindow(); - m_task_manager_window->raise(); -} - -void Application::close_task_manager_window() -{ - if (m_task_manager_window) { - m_task_manager_window->close(); - delete m_task_manager_window; - m_task_manager_window = nullptr; - } -} - BrowserWindow& Application::new_window(Vector const& initial_urls, BrowserWindow::IsPopupWindow is_popup_window, Tab* parent_tab, Optional page_index) { auto* window = new BrowserWindow(initial_urls, is_popup_window, parent_tab, move(page_index)); diff --git a/UI/Qt/Application.h b/UI/Qt/Application.h index 48c5bb6ac91..6daa98c7333 100644 --- a/UI/Qt/Application.h +++ b/UI/Qt/Application.h @@ -30,9 +30,6 @@ public: BrowserWindow& new_window(Vector const& initial_urls, BrowserWindow::IsPopupWindow is_popup_window = BrowserWindow::IsPopupWindow::No, Tab* parent_tab = nullptr, Optional page_index = {}); - void show_task_manager_window(); - void close_task_manager_window(); - BrowserWindow& active_window() { return *m_active_window; } void set_active_window(BrowserWindow& w) { m_active_window = &w; } @@ -41,7 +38,6 @@ private: virtual Optional ask_user_for_download_folder() const override; - TaskManagerWindow* m_task_manager_window { nullptr }; BrowserWindow* m_active_window { nullptr }; }; diff --git a/UI/Qt/BrowserWindow.cpp b/UI/Qt/BrowserWindow.cpp index a83f25761d0..1259b84aec8 100644 --- a/UI/Qt/BrowserWindow.cpp +++ b/UI/Qt/BrowserWindow.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -383,8 +382,8 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow task_manager_action->setIcon(load_icon_from_uri("resource://icons/16x16/app-system-monitor.png"sv)); task_manager_action->setShortcuts({ QKeySequence("Ctrl+Shift+M") }); inspect_menu->addAction(task_manager_action); - QObject::connect(task_manager_action, &QAction::triggered, this, [&] { - static_cast(QApplication::instance())->show_task_manager_window(); + QObject::connect(task_manager_action, &QAction::triggered, this, [this]() { + new_tab_from_url(URL::URL::about("processes"_string), Web::HTML::ActivateTab::Yes); }); auto* debug_menu = m_hamburger_menu->addMenu("&Debug"); diff --git a/UI/Qt/BrowserWindow.h b/UI/Qt/BrowserWindow.h index ca455c6496d..9ad69c44a27 100644 --- a/UI/Qt/BrowserWindow.h +++ b/UI/Qt/BrowserWindow.h @@ -25,7 +25,6 @@ namespace Ladybird { class SettingsDialog; class WebContentView; -class TaskManagerWindow; class BrowserWindow : public QMainWindow { Q_OBJECT diff --git a/UI/Qt/CMakeLists.txt b/UI/Qt/CMakeLists.txt index 8da50577220..79ba60b1f91 100644 --- a/UI/Qt/CMakeLists.txt +++ b/UI/Qt/CMakeLists.txt @@ -10,7 +10,6 @@ target_sources(ladybird PRIVATE SettingsDialog.cpp Tab.cpp TabBar.cpp - TaskManagerWindow.cpp TVGIconEngine.cpp StringUtils.cpp WebContentView.cpp diff --git a/UI/Qt/TaskManagerWindow.cpp b/UI/Qt/TaskManagerWindow.cpp deleted file mode 100644 index c4cee5f5d7a..00000000000 --- a/UI/Qt/TaskManagerWindow.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2024, Andrew Kaster - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -#include - -namespace Ladybird { - -TaskManagerWindow::TaskManagerWindow(QWidget* parent) - : QWidget(parent, Qt::WindowFlags(Qt::WindowType::Window)) - , m_web_view(new WebContentView(this)) -{ - setLayout(new QVBoxLayout); - layout()->addWidget(m_web_view); - - setWindowTitle("Task Manager"); - resize(600, 400); - - m_update_timer.setInterval(1000); - - QObject::connect(&m_update_timer, &QTimer::timeout, [this] { - this->update_statistics(); - }); - - update_statistics(); -} - -void TaskManagerWindow::showEvent(QShowEvent*) -{ - m_update_timer.start(); -} - -void TaskManagerWindow::hideEvent(QHideEvent*) -{ - m_update_timer.stop(); -} - -void TaskManagerWindow::update_statistics() -{ - WebView::Application::the().update_process_statistics(); - m_web_view->load_html(WebView::Application::the().generate_process_statistics_html()); -} - -} diff --git a/UI/Qt/TaskManagerWindow.h b/UI/Qt/TaskManagerWindow.h deleted file mode 100644 index 8084aaa9fdc..00000000000 --- a/UI/Qt/TaskManagerWindow.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2024, Andrew Kaster - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -#include -#include - -namespace Ladybird { - -class TaskManagerWindow : public QWidget { - Q_OBJECT - -public: - explicit TaskManagerWindow(QWidget* parent); - -private: - virtual void showEvent(QShowEvent*) override; - virtual void hideEvent(QHideEvent*) override; - - void update_statistics(); - - WebContentView* m_web_view { nullptr }; - QTimer m_update_timer; -}; - -} From 942f26a84663e291ef402f68a8f95fbb9ba8ba39 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 16 Mar 2025 12:43:23 -0400 Subject: [PATCH 085/141] LibWebView: Remove now-unused ProcessManager HTML generator --- Libraries/LibWebView/Application.cpp | 10 --- Libraries/LibWebView/Application.h | 4 -- Libraries/LibWebView/ProcessManager.cpp | 84 ------------------------- Libraries/LibWebView/ProcessManager.h | 3 - 4 files changed, 101 deletions(-) diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index faed92da45b..fb3d39c194c 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -321,16 +321,6 @@ Optional Application::find_process(pid_t pid) return m_process_manager.find_process(pid); } -void Application::update_process_statistics() -{ - m_process_manager.update_all_process_statistics(); -} - -String Application::generate_process_statistics_html() -{ - return m_process_manager.generate_html(); -} - void Application::send_updated_process_statistics_to_view(ViewImplementation& view) { m_process_manager.update_all_process_statistics(); diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 4d46b1d6a80..055c78669c5 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -56,10 +56,6 @@ public: #endif Optional find_process(pid_t); - // FIXME: Should we just expose the ProcessManager via a getter? - void update_process_statistics(); - String generate_process_statistics_html(); - void send_updated_process_statistics_to_view(ViewImplementation&); ErrorOr path_for_downloaded_file(StringView file) const; diff --git a/Libraries/LibWebView/ProcessManager.cpp b/Libraries/LibWebView/ProcessManager.cpp index e3a0352baf3..8114cba2fd1 100644 --- a/Libraries/LibWebView/ProcessManager.cpp +++ b/Libraries/LibWebView/ProcessManager.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -121,89 +120,6 @@ void ProcessManager::update_all_process_statistics() (void)update_process_statistics(m_statistics); } -String ProcessManager::generate_html() -{ - Threading::MutexLocker locker { m_lock }; - StringBuilder builder; - - builder.append(R"( - - - Task Manager - - - - - - - - - - - - - - )"sv); - - m_statistics.for_each_process([&](auto const& process) { - builder.append(""sv); - builder.append(""sv); - builder.append(""sv); - builder.append(""sv); - builder.append(""sv); - builder.append(""sv); - }); - - builder.append(R"( - -
NamePIDMemory UsageCPU %
"sv); - auto& process_handle = this->find_process(process.pid).value(); - builder.append(WebView::process_name_from_type(process_handle.type())); - if (process_handle.title().has_value()) - builder.appendff(" - {}", escape_html_entities(*process_handle.title())); - builder.append(""sv); - builder.append(String::number(process.pid)); - builder.append(""sv); - builder.append(human_readable_size(process.memory_usage_bytes)); - builder.append(""sv); - builder.append(MUST(String::formatted("{:.1f}", process.cpu_percent))); - builder.append("
- - - )"sv); - - return builder.to_string_without_validation(); -} - String ProcessManager::serialize_json() { Threading::MutexLocker locker { m_lock }; diff --git a/Libraries/LibWebView/ProcessManager.h b/Libraries/LibWebView/ProcessManager.h index 478b9f56341..f501718f129 100644 --- a/Libraries/LibWebView/ProcessManager.h +++ b/Libraries/LibWebView/ProcessManager.h @@ -7,8 +7,6 @@ #pragma once #include -#include -#include #include #include #include @@ -36,7 +34,6 @@ public: #endif void update_all_process_statistics(); - String generate_html(); String serialize_json(); Function on_process_exited; From d38b5e260eb8d929177116f4fcd493e0945d3a8c Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Wed, 19 Mar 2025 13:18:08 +0000 Subject: [PATCH 086/141] LibWeb: Mark `CSSImportRule.styleSheet` IDL definition as nullable The stylesheet may be null if its `supports()` condition does not match. This matches the current specification. --- Libraries/LibWeb/CSS/CSSImportRule.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/CSS/CSSImportRule.idl b/Libraries/LibWeb/CSS/CSSImportRule.idl index fd8b073129b..d474386d076 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.idl +++ b/Libraries/LibWeb/CSS/CSSImportRule.idl @@ -7,7 +7,7 @@ interface CSSImportRule : CSSRule { readonly attribute USVString href; // FIXME: [SameObject, PutForwards=mediaText] readonly attribute MediaList media; - [SameObject, ImplementedAs=style_sheet_for_bindings] readonly attribute CSSStyleSheet styleSheet; + [SameObject, ImplementedAs=style_sheet_for_bindings] readonly attribute CSSStyleSheet? styleSheet; [FIXME] readonly attribute CSSOMString? layerName; [FIXME] readonly attribute CSSOMString? supportsText; }; From c37a47f76f7aa43b6e523a4516cf05817c85b989 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Wed, 19 Mar 2025 11:23:33 +0000 Subject: [PATCH 087/141] LibWeb/CSS: Implement import at-rule supports conditions This indicates the features the browser must support for the given stylesheet to be fetched. --- Libraries/LibWeb/CSS/CSSImportRule.cpp | 17 ++++++++--- Libraries/LibWeb/CSS/CSSImportRule.h | 5 ++-- Libraries/LibWeb/CSS/Parser/RuleParsing.cpp | 25 ++++++++++++++-- .../ref-filled-green-100px-square.xht | 19 ++++++++++++ .../css-cascade/import-conditional-002.html | 30 +++++++++++++++++++ .../css/css-cascade/support/test-green.css | 4 +++ .../css/css-cascade/support/test-red.css | 4 +++ 7 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-cascade/reference/ref-filled-green-100px-square.xht create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/import-conditional-002.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-green.css create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-red.css diff --git a/Libraries/LibWeb/CSS/CSSImportRule.cpp b/Libraries/LibWeb/CSS/CSSImportRule.cpp index e3340cd7960..9c9d77088e3 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.cpp +++ b/Libraries/LibWeb/CSS/CSSImportRule.cpp @@ -23,16 +23,17 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSImportRule); -GC::Ref CSSImportRule::create(URL::URL url, DOM::Document& document) +GC::Ref CSSImportRule::create(URL::URL url, DOM::Document& document, RefPtr supports) { auto& realm = document.realm(); - return realm.create(move(url), document); + return realm.create(move(url), document, supports); } -CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document) +CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document, RefPtr supports) : CSSRule(document.realm(), Type::Import) , m_url(move(url)) , m_document(document) + , m_supports(supports) { } @@ -70,6 +71,11 @@ String CSSImportRule::serialized() const // 2. The result of performing serialize a URL on the rule’s location. serialize_a_url(builder, m_url.to_string()); + // AD-HOC: Serialize the rule's supports condition if it exists. + // This isn't currently specified, but major browsers include this in their serialization of import rules + if (m_supports) + builder.appendff(" supports({})", m_supports->to_string()); + // FIXME: 3. If the rule’s associated media list is not empty, a single SPACE (U+0020) followed by the result of performing serialize a media query list on the media list. // 4. The string ";", i.e., SEMICOLON (U+003B). @@ -88,7 +94,10 @@ void CSSImportRule::fetch() VERIFY(parent_style_sheet()); auto& parent_style_sheet = *this->parent_style_sheet(); - // FIXME: 2. If rule has a , and that condition is not true, return. + // 2. If rule has a , and that condition is not true, return. + if (m_supports && !m_supports->matches()) { + return; + } // 3. Let parsedUrl be the result of the URL parser steps with rule’s URL and parentStylesheet’s location. // If the algorithm returns an error, return. [CSSOM] diff --git a/Libraries/LibWeb/CSS/CSSImportRule.h b/Libraries/LibWeb/CSS/CSSImportRule.h index cd5f971ea04..bfb6a2dcbe7 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.h +++ b/Libraries/LibWeb/CSS/CSSImportRule.h @@ -21,7 +21,7 @@ class CSSImportRule final GC_DECLARE_ALLOCATOR(CSSImportRule); public: - [[nodiscard]] static GC::Ref create(URL::URL, DOM::Document&); + [[nodiscard]] static GC::Ref create(URL::URL, DOM::Document&, RefPtr); virtual ~CSSImportRule() = default; @@ -34,7 +34,7 @@ public: CSSStyleSheet* style_sheet_for_bindings() { return m_style_sheet; } private: - CSSImportRule(URL::URL, DOM::Document&); + CSSImportRule(URL::URL, DOM::Document&, RefPtr); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -48,6 +48,7 @@ private: URL::URL m_url; GC::Ptr m_document; + RefPtr m_supports; GC::Ptr m_style_sheet; Optional m_document_load_event_delayer; }; diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index b4be2aac584..622929ba722 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -161,16 +161,35 @@ GC::Ptr Parser::convert_to_import_rule(AtRule const& rule) } tokens.discard_whitespace(); - // TODO: Support layers and import-conditions + // FIXME: Implement layer support. + RefPtr supports {}; + if (tokens.next_token().is_function("supports"sv)) { + auto component_value = tokens.consume_a_token(); + TokenStream supports_tokens { component_value.function().value }; + if (supports_tokens.next_token().is_block()) { + supports = parse_a_supports(supports_tokens); + } else { + m_rule_context.append(ContextType::SupportsCondition); + auto declaration = consume_a_declaration(supports_tokens); + m_rule_context.take_last(); + if (declaration.has_value()) { + auto supports_declaration = Supports::Declaration::create(declaration->to_string(), convert_to_style_property(*declaration).has_value()); + supports = Supports::create(supports_declaration.release_nonnull()); + } + } + } + + // FIXME: Implement media query support. + if (tokens.has_next_token()) { if constexpr (CSS_PARSER_DEBUG) { - dbgln("Failed to parse @import rule: Trailing tokens after URL are not yet supported."); + dbgln("Failed to parse @import rule:"); tokens.dump_all_tokens(); } return {}; } - return CSSImportRule::create(url.value(), const_cast(*document())); + return CSSImportRule::create(url.value(), const_cast(*document()), supports); } Optional Parser::parse_layer_name(TokenStream& tokens, AllowBlankLayerName allow_blank_layer_name) diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-cascade/reference/ref-filled-green-100px-square.xht b/Tests/LibWeb/Ref/expected/wpt-import/css/css-cascade/reference/ref-filled-green-100px-square.xht new file mode 100644 index 00000000000..05a13794482 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-cascade/reference/ref-filled-green-100px-square.xht @@ -0,0 +1,19 @@ + + + + CSS Reftest Reference + + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/import-conditional-002.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/import-conditional-002.html new file mode 100644 index 00000000000..08ba12f54f8 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/import-conditional-002.html @@ -0,0 +1,30 @@ + + + + + CSS Cascade: @import with basic supports condition + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-green.css b/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-green.css new file mode 100644 index 00000000000..da8e1014d2a --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-green.css @@ -0,0 +1,4 @@ +.test { + background: green; + color: green; +} diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-red.css b/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-red.css new file mode 100644 index 00000000000..bb309fcd570 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-cascade/support/test-red.css @@ -0,0 +1,4 @@ +.test { + background: red; + color: red; +} From 5d57723ebf85c58e38166859c70d09dfc96110c9 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Wed, 19 Mar 2025 11:32:34 +0000 Subject: [PATCH 088/141] LibWeb: Implement `CSSImportRule.supportsText` Returns the supports condition specified by the given import at-rule. --- Libraries/LibWeb/CSS/CSSImportRule.cpp | 7 +++++++ Libraries/LibWeb/CSS/CSSImportRule.h | 2 ++ Libraries/LibWeb/CSS/CSSImportRule.idl | 2 +- Libraries/LibWeb/CSS/Parser/Parser.cpp | 4 +++- Libraries/LibWeb/CSS/Supports.cpp | 2 +- .../expected/css/CSSImportRule-supportsText.txt | 4 ++++ .../input/css/CSSImportRule-supportsText.html | 17 +++++++++++++++++ 7 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/css/CSSImportRule-supportsText.txt create mode 100644 Tests/LibWeb/Text/input/css/CSSImportRule-supportsText.html diff --git a/Libraries/LibWeb/CSS/CSSImportRule.cpp b/Libraries/LibWeb/CSS/CSSImportRule.cpp index 9c9d77088e3..f0cc8d7fa8d 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.cpp +++ b/Libraries/LibWeb/CSS/CSSImportRule.cpp @@ -171,4 +171,11 @@ void CSSImportRule::set_style_sheet(GC::Ref style_sheet) m_document->invalidate_style(DOM::StyleInvalidationReason::CSSImportRule); } +Optional CSSImportRule::supports_text() const +{ + if (!m_supports) + return {}; + return m_supports->to_string(); +} + } diff --git a/Libraries/LibWeb/CSS/CSSImportRule.h b/Libraries/LibWeb/CSS/CSSImportRule.h index bfb6a2dcbe7..e6989188074 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.h +++ b/Libraries/LibWeb/CSS/CSSImportRule.h @@ -33,6 +33,8 @@ public: CSSStyleSheet const* loaded_style_sheet() const { return m_style_sheet; } CSSStyleSheet* style_sheet_for_bindings() { return m_style_sheet; } + Optional supports_text() const; + private: CSSImportRule(URL::URL, DOM::Document&, RefPtr); diff --git a/Libraries/LibWeb/CSS/CSSImportRule.idl b/Libraries/LibWeb/CSS/CSSImportRule.idl index d474386d076..d6f7446e4af 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.idl +++ b/Libraries/LibWeb/CSS/CSSImportRule.idl @@ -9,5 +9,5 @@ interface CSSImportRule : CSSRule { // FIXME: [SameObject, PutForwards=mediaText] readonly attribute MediaList media; [SameObject, ImplementedAs=style_sheet_for_bindings] readonly attribute CSSStyleSheet? styleSheet; [FIXME] readonly attribute CSSOMString? layerName; - [FIXME] readonly attribute CSSOMString? supportsText; + readonly attribute CSSOMString? supportsText; }; diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index f54ed099c31..f01ec0aa5ff 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -291,9 +291,11 @@ OwnPtr Parser::parse_supports_feature(TokenStreamto_string(), convert_to_style_property(*declaration).has_value()); + + return BooleanExpressionInParens::create(supports_declaration.release_nonnull()); } } diff --git a/Libraries/LibWeb/CSS/Supports.cpp b/Libraries/LibWeb/CSS/Supports.cpp index 99112242623..09ef0411099 100644 --- a/Libraries/LibWeb/CSS/Supports.cpp +++ b/Libraries/LibWeb/CSS/Supports.cpp @@ -22,7 +22,7 @@ MatchResult Supports::Declaration::evaluate(HTML::Window const*) const String Supports::Declaration::to_string() const { - return MUST(String::formatted("({})", m_declaration)); + return m_declaration; } void Supports::Declaration::dump(StringBuilder& builder, int indent_levels) const diff --git a/Tests/LibWeb/Text/expected/css/CSSImportRule-supportsText.txt b/Tests/LibWeb/Text/expected/css/CSSImportRule-supportsText.txt new file mode 100644 index 00000000000..52162806d0d --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/CSSImportRule-supportsText.txt @@ -0,0 +1,4 @@ +cssText: @import url("https://something.invalid/") supports(foo: bar); +supportsText: foo: bar +cssText: @import url("https://something.invalid/") supports((display: block) and (foo: bar)); +supportsText: (display: block) and (foo: bar) diff --git a/Tests/LibWeb/Text/input/css/CSSImportRule-supportsText.html b/Tests/LibWeb/Text/input/css/CSSImportRule-supportsText.html new file mode 100644 index 00000000000..6704b7af171 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/CSSImportRule-supportsText.html @@ -0,0 +1,17 @@ + + + + From 2f6de5d9ac19cb73920e42a3629b8773584058ad Mon Sep 17 00:00:00 2001 From: "Psychpsyo (Cameron)" <60073468+Psychpsyo@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:21:19 +0100 Subject: [PATCH 089/141] LibWeb: Import typo fix from the spec --- Libraries/LibWeb/HTML/Window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index ba3b173f280..231c5138f2e 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -1223,7 +1223,7 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme // 1. Parse pseudoElt as a , and let type be the result. auto type = parse_pseudo_element_selector(CSS::Parser::ParsingParams(associated_document()), pseudo_element.value()); - // 2. If type is failure, or is an ::slotted() or ::part() pseudo-element, let obj be null. + // 2. If type is failure, or is a ::slotted() or ::part() pseudo-element, let obj be null. // FIXME: We can't pass a null element to CSSStyleProperties::create_resolved_style() if (!type.has_value()) { } From ddcd48d12e3a319b5d8ec10da375b215e7af83be Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Wed, 19 Mar 2025 16:46:06 -0600 Subject: [PATCH 090/141] Documentation: Update build instructions to remove -GXcode suggestion Also some other assorted docs cleanup. --- Documentation/BuildInstructionsLadybird.md | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Documentation/BuildInstructionsLadybird.md b/Documentation/BuildInstructionsLadybird.md index 57134c01c14..cc4addd8165 100644 --- a/Documentation/BuildInstructionsLadybird.md +++ b/Documentation/BuildInstructionsLadybird.md @@ -129,14 +129,14 @@ cmake -GNinja -BBuild/release Finally, run `ninja` (or the generator you're using) to start the build: ``` -ninja -CBuild/release +ninja -C Build/release ``` For more information, see [Custom CMake build directory](#custom-cmake-build-directory) and [Running manually](#running-manually). ### macOS: -Xcode 14 versions before 14.3 might crash while building ladybird. Xcode 14.3 or clang from homebrew may be required to successfully build ladybird. +Xcode 15 or clang from homebrew is required to successfully build ladybird. ``` xcode-select --install @@ -214,7 +214,7 @@ The simplest way to build and run ladybird is via the ladybird.sh script: ```bash # From /path/to/ladybird -./Meta/ladybird.sh run ladybird +./Meta/ladybird.sh run ``` On macOS, to build using clang from homebrew: @@ -231,11 +231,14 @@ The above commands will build a Release version of Ladybird. To instead build a `Meta/ladybird.sh` script with the value of the `BUILD_PRESET` environment variable set to `Debug`, like this: ```bash -BUILD_PRESET=Debug ./Meta/ladybird.sh run ladybird +BUILD_PRESET=Debug ./Meta/ladybird.sh run ``` Note that debug symbols are available in both Release and Debug builds. +If you want to run other applications, such as the headless-browser, the JS REPL, or the WebAssembly REPL, specify an +executable with `./Meta/ladybird.sh run `. + ### The User Interfaces Ladybird will be built with one of the following browser frontends, depending on the platform: @@ -305,9 +308,9 @@ a suitable C++ compiler (g++ >= 13, clang >= 14, Apple Clang >= 14.3) via the CM CMAKE_C_COMPILER cmake options. ``` -cmake -GNinja -B MyBuildDir +cmake --preset default -B MyBuildDir # optionally, add -DCMAKE_CXX_COMPILER= -DCMAKE_C_COMPILER= -cmake --build MyBuildDir +cmake --build --preset default MyBuildDir ninja -C MyBuildDir run-ladybird ``` @@ -367,15 +370,8 @@ Simply run the `ladybird.sh` script as normal, and then make sure to codesign th Now you can open the Instruments app and point it to the Ladybird app bundle. -If you want to use Xcode itself for debugging, you will need to generate an Xcode project. -The `ladybird.sh` build script does not know how to generate Xcode projects, so creating the project must be done manually. - -``` -cmake -GXcode -B Build/release -``` - -After generating an Xcode project into the specified build directory, you can open `ladybird.xcodeproj` in Xcode. The project has a ton of targets, many of which are generated code. -The only target that needs a scheme is the ladybird app bundle. +Building the project with Xcode is not supported. The Xcode project generated by CMake does not properly execute custom +targets, and does not handle all target names in the project. ### Building on OpenIndiana From 10db20a537ff9ef98664628520e0a4cea2e793a5 Mon Sep 17 00:00:00 2001 From: stasoid Date: Thu, 13 Feb 2025 10:43:55 +0500 Subject: [PATCH 091/141] LibCore: Implement System::current_executable_path on Windows --- Libraries/LibCore/CMakeLists.txt | 7 ++++--- Libraries/LibCore/Process.h | 1 + Libraries/LibCore/SystemWindows.cpp | 6 ++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Libraries/LibCore/CMakeLists.txt b/Libraries/LibCore/CMakeLists.txt index d35df5055cb..89ea1d7a4b6 100644 --- a/Libraries/LibCore/CMakeLists.txt +++ b/Libraries/LibCore/CMakeLists.txt @@ -13,10 +13,13 @@ set(SOURCES if (WIN32) list(APPEND SOURCES + ProcessWindows.cpp SocketpairWindows.cpp SystemWindows.cpp) else() - list(APPEND SOURCES System.cpp) + list(APPEND SOURCES + Process.cpp + System.cpp) endif() serenity_lib(LibCoreMinimal coreminimal) @@ -52,14 +55,12 @@ set(SOURCES if (WIN32) # FIXME: Support UDPServer and TCPServer on Windows list(APPEND SOURCES - ProcessWindows.cpp SocketWindows.cpp AnonymousBufferWindows.cpp EventLoopImplementationWindows.cpp) else() list(APPEND SOURCES Command.cpp - Process.cpp Socket.cpp AnonymousBuffer.cpp EventLoopImplementationUnix.cpp diff --git a/Libraries/LibCore/Process.h b/Libraries/LibCore/Process.h index 7ae9b571801..30f3cb8b93e 100644 --- a/Libraries/LibCore/Process.h +++ b/Libraries/LibCore/Process.h @@ -11,6 +11,7 @@ #include #include +#include #include namespace Core { diff --git a/Libraries/LibCore/SystemWindows.cpp b/Libraries/LibCore/SystemWindows.cpp index fec598601ef..f46508f7a37 100644 --- a/Libraries/LibCore/SystemWindows.cpp +++ b/Libraries/LibCore/SystemWindows.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -263,4 +264,9 @@ u64 physical_memory_bytes() return ms.ullTotalPhys; } +ErrorOr current_executable_path() +{ + return TRY(Process::get_name()).to_byte_string(); +} + } From 2e200489c83da4d0ac7c5a7a8058c07af182b363 Mon Sep 17 00:00:00 2001 From: stasoid Date: Thu, 13 Feb 2025 10:46:38 +0500 Subject: [PATCH 092/141] LibCore: Implement StandardPaths::user_data_directory on Windows --- Libraries/LibCore/StandardPaths.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/LibCore/StandardPaths.cpp b/Libraries/LibCore/StandardPaths.cpp index 93c21986821..1e6fe5818b9 100644 --- a/Libraries/LibCore/StandardPaths.cpp +++ b/Libraries/LibCore/StandardPaths.cpp @@ -157,8 +157,7 @@ ByteString StandardPaths::config_directory() ByteString StandardPaths::user_data_directory() { #ifdef AK_OS_WINDOWS - dbgln("Core::StandardPaths::user_data_directory() is not implemented"); - VERIFY_NOT_REACHED(); + return ByteString::formatted("{}/Ladybird"sv, getenv("LOCALAPPDATA")); #endif if (auto data_directory = get_environment_if_not_empty("XDG_DATA_HOME"sv); data_directory.has_value()) return LexicalPath::canonicalized_path(*data_directory); From 2abc792938747dce23bea1e495d2d4e6095204f6 Mon Sep 17 00:00:00 2001 From: stasoid Date: Thu, 13 Feb 2025 18:50:23 +0500 Subject: [PATCH 093/141] LibCore: Implement System::set_close_on_exec --- Libraries/LibCore/Socket.cpp | 10 +--------- Libraries/LibCore/SocketWindows.cpp | 4 +--- Libraries/LibCore/System.cpp | 13 +++++++++++++ Libraries/LibCore/System.h | 1 + Libraries/LibCore/SystemWindows.cpp | 7 +++++++ Libraries/LibIPC/File.cpp | 16 +--------------- Libraries/LibIPC/File.h | 8 +++++++- Libraries/LibIPC/FileWindows.cpp | 7 ------- 8 files changed, 31 insertions(+), 35 deletions(-) diff --git a/Libraries/LibCore/Socket.cpp b/Libraries/LibCore/Socket.cpp index 1d1b9eb6919..dc5af41edc3 100644 --- a/Libraries/LibCore/Socket.cpp +++ b/Libraries/LibCore/Socket.cpp @@ -193,15 +193,7 @@ ErrorOr PosixSocketHelper::set_blocking(bool enabled) ErrorOr PosixSocketHelper::set_close_on_exec(bool enabled) { - int flags = TRY(System::fcntl(m_fd, F_GETFD)); - - if (enabled) - flags |= FD_CLOEXEC; - else - flags &= ~FD_CLOEXEC; - - TRY(System::fcntl(m_fd, F_SETFD, flags)); - return {}; + return System::set_close_on_exec(m_fd, enabled); } ErrorOr PosixSocketHelper::set_receive_timeout(AK::Duration timeout) diff --git a/Libraries/LibCore/SocketWindows.cpp b/Libraries/LibCore/SocketWindows.cpp index 33b83b2f79e..65d9217ec42 100644 --- a/Libraries/LibCore/SocketWindows.cpp +++ b/Libraries/LibCore/SocketWindows.cpp @@ -106,9 +106,7 @@ ErrorOr PosixSocketHelper::set_blocking(bool) ErrorOr PosixSocketHelper::set_close_on_exec(bool enabled) { - if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT)) - return Error::from_windows_error(); - return {}; + return System::set_close_on_exec(m_fd, enabled); } ErrorOr PosixSocketHelper::pending_bytes() const diff --git a/Libraries/LibCore/System.cpp b/Libraries/LibCore/System.cpp index 586a3a9070c..1d37a7041f1 100644 --- a/Libraries/LibCore/System.cpp +++ b/Libraries/LibCore/System.cpp @@ -1017,4 +1017,17 @@ ErrorOr sleep_ms(u32 milliseconds) return {}; } +ErrorOr set_close_on_exec(int fd, bool enabled) +{ + int flags = TRY(fcntl(fd, F_GETFD)); + + if (enabled) + flags |= FD_CLOEXEC; + else + flags &= ~FD_CLOEXEC; + + TRY(fcntl(fd, F_SETFD, flags)); + return {}; +} + } diff --git a/Libraries/LibCore/System.h b/Libraries/LibCore/System.h index 323982dd880..0a0b3eb122b 100644 --- a/Libraries/LibCore/System.h +++ b/Libraries/LibCore/System.h @@ -184,5 +184,6 @@ ErrorOr set_resource_limits(int resource, rlim_t limit); int getpid(); bool is_socket(int fd); ErrorOr sleep_ms(u32 milliseconds); +ErrorOr set_close_on_exec(int fd, bool enabled); } diff --git a/Libraries/LibCore/SystemWindows.cpp b/Libraries/LibCore/SystemWindows.cpp index f46508f7a37..d5b021bf9cf 100644 --- a/Libraries/LibCore/SystemWindows.cpp +++ b/Libraries/LibCore/SystemWindows.cpp @@ -269,4 +269,11 @@ ErrorOr current_executable_path() return TRY(Process::get_name()).to_byte_string(); } +ErrorOr set_close_on_exec(int handle, bool enabled) +{ + if (!SetHandleInformation(to_handle(handle), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT)) + return Error::from_windows_error(); + return {}; +} + } diff --git a/Libraries/LibIPC/File.cpp b/Libraries/LibIPC/File.cpp index 148a9c7d849..6f664564059 100644 --- a/Libraries/LibIPC/File.cpp +++ b/Libraries/LibIPC/File.cpp @@ -11,25 +11,11 @@ namespace IPC { -// FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding. -// Perhaps we should add an option to IPC::File to allow the receiver to decide whether to -// make it O_CLOEXEC or not. Or an attribute in the .ipc file? -ErrorOr File::clear_close_on_exec() -{ - auto fd_flags = TRY(Core::System::fcntl(m_fd, F_GETFD)); - fd_flags &= ~FD_CLOEXEC; - TRY(Core::System::fcntl(m_fd, F_SETFD, fd_flags)); - return {}; -} - template<> ErrorOr decode(Decoder& decoder) { auto file = TRY(decoder.files().try_dequeue()); - auto fd = file.fd(); - - auto fd_flags = TRY(Core::System::fcntl(fd, F_GETFD)); - TRY(Core::System::fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC)); + TRY(Core::System::set_close_on_exec(file.fd(), true)); return file; } diff --git a/Libraries/LibIPC/File.h b/Libraries/LibIPC/File.h index 77c5f3d86be..5aa1001010e 100644 --- a/Libraries/LibIPC/File.h +++ b/Libraries/LibIPC/File.h @@ -63,7 +63,13 @@ public: return exchange(m_fd, -1); } - ErrorOr clear_close_on_exec(); + // FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding. + // Perhaps we should add an option to IPC::File to allow the receiver to decide whether to + // make it O_CLOEXEC or not. Or an attribute in the .ipc file? + ErrorOr clear_close_on_exec() + { + return Core::System::set_close_on_exec(m_fd, false); + } private: explicit File(int fd) diff --git a/Libraries/LibIPC/FileWindows.cpp b/Libraries/LibIPC/FileWindows.cpp index 2999e1f1847..dfc7be512de 100644 --- a/Libraries/LibIPC/FileWindows.cpp +++ b/Libraries/LibIPC/FileWindows.cpp @@ -12,13 +12,6 @@ namespace IPC { -ErrorOr File::clear_close_on_exec() -{ - if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) - return Error::from_windows_error(); - return {}; -} - template<> ErrorOr decode(Decoder& decoder) { From 2dd657f53032da045403d3bbbe73f5421dbde1ec Mon Sep 17 00:00:00 2001 From: stasoid Date: Sat, 21 Dec 2024 16:19:39 +0500 Subject: [PATCH 094/141] LibWebView: Port to Windows --- Libraries/LibWebView/Application.cpp | 2 ++ Libraries/LibWebView/BrowserProcess.cpp | 7 +++--- Libraries/LibWebView/Process.cpp | 29 ++++++++++++++++++------- Libraries/LibWebView/ProcessManager.cpp | 6 +++++ Libraries/LibWebView/Utilities.cpp | 2 +- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index fb3d39c194c..cd866dfc055 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -59,9 +59,11 @@ Application::~Application() void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url) { +#ifndef AK_OS_WINDOWS // Increase the open file limit, as the default limits on Linux cause us to run out of file descriptors with around 15 tabs open. if (auto result = Core::System::set_resource_limits(RLIMIT_NOFILE, 8192); result.is_error()) warnln("Unable to increase open file limit: {}", result.error()); +#endif Vector raw_urls; Vector certificates; diff --git a/Libraries/LibWebView/BrowserProcess.cpp b/Libraries/LibWebView/BrowserProcess.cpp index 1dfbf761299..f2a8296c2ea 100644 --- a/Libraries/LibWebView/BrowserProcess.cpp +++ b/Libraries/LibWebView/BrowserProcess.cpp @@ -40,15 +40,15 @@ ErrorOr BrowserProcess::connect(Vectorwrite_until_depleted(ByteString::number(::getpid()))); + TRY(m_pid_file->write_until_depleted(ByteString::number(Core::System::getpid()))); return ProcessDisposition::ContinueMainProcess; } ErrorOr BrowserProcess::connect_as_client(ByteString const& socket_path, Vector const& raw_urls, NewWindow new_window) { + // TODO: Mach IPC auto socket = TRY(Core::LocalSocket::connect(socket_path)); - static_assert(IsSame, "Need to handle other IPC transports here"); auto client = UIProcessClient::construct(IPC::Transport(move(socket))); if (new_window == NewWindow::Yes) { @@ -64,8 +64,7 @@ ErrorOr BrowserProcess::connect_as_client(ByteString const& socket_path, V ErrorOr BrowserProcess::connect_as_server(ByteString const& socket_path) { - static_assert(IsSame, "Need to handle other IPC transports here"); - + // TODO: Mach IPC auto socket_fd = TRY(Process::create_ipc_socket(socket_path)); m_socket_path = socket_path; auto local_server = TRY(Core::LocalServer::try_create()); diff --git a/Libraries/LibWebView/Process.cpp b/Libraries/LibWebView/Process.cpp index e5ba8ebd6d9..2f31aece3be 100644 --- a/Libraries/LibWebView/Process.cpp +++ b/Libraries/LibWebView/Process.cpp @@ -27,7 +27,7 @@ Process::~Process() ErrorOr Process::spawn_and_connect_to_process(Core::ProcessSpawnOptions const& options) { - static_assert(IsSame, "Need to handle other IPC transports here"); + // TODO: Mach IPC int socket_fds[2] {}; TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds)); @@ -35,8 +35,8 @@ ErrorOr Process::spawn_and_connect_to_process(C ArmedScopeGuard guard_fd_0 { [&] { MUST(Core::System::close(socket_fds[0])); } }; ArmedScopeGuard guard_fd_1 { [&] { MUST(Core::System::close(socket_fds[1])); } }; - auto& file_actions = const_cast(options).file_actions; - file_actions.append(Core::FileAction::CloseFile { socket_fds[0] }); + // Note: Core::System::socketpair creates inheritable sockets both on Linux and Windows unless SOCK_CLOEXEC is specified. + TRY(Core::System::set_close_on_exec(socket_fds[0], true)); auto takeover_string = MUST(String::formatted("{}:{}", options.name, socket_fds[1])); TRY(Core::Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Core::Environment::Overwrite::Yes)); @@ -50,6 +50,18 @@ ErrorOr Process::spawn_and_connect_to_process(C return ProcessAndIPCTransport { move(process), IPC::Transport(move(ipc_socket)) }; } +#ifdef AK_OS_WINDOWS +// FIXME: Implement WebView::Process::get_process_pid on Windows +ErrorOr> Process::get_process_pid(StringView, StringView) +{ + VERIFY(0 && "WebView::Process::get_process_pid is not implemented"); +} +// FIXME: Implement WebView::Process::create_ipc_socket on Windows +ErrorOr Process::create_ipc_socket(ByteString const&) +{ + VERIFY(0 && "WebView::Process::create_ipc_socket is not implemented"); +} +#else ErrorOr> Process::get_process_pid(StringView process_name, StringView pid_path) { if (Core::System::stat(pid_path).is_error()) @@ -92,19 +104,19 @@ ErrorOr Process::create_ipc_socket(ByteString const& socket_path) if (!Core::System::stat(socket_path).is_error()) TRY(Core::System::unlink(socket_path)); -#ifdef SOCK_NONBLOCK +# ifdef SOCK_NONBLOCK auto socket_fd = TRY(Core::System::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); -#else +# else auto socket_fd = TRY(Core::System::socket(AF_LOCAL, SOCK_STREAM, 0)); int option = 1; TRY(Core::System::ioctl(socket_fd, FIONBIO, &option)); TRY(Core::System::fcntl(socket_fd, F_SETFD, FD_CLOEXEC)); -#endif +# endif -#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_GNU_HURD) +# if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_GNU_HURD) TRY(Core::System::fchmod(socket_fd, 0600)); -#endif +# endif auto socket_address = Core::SocketAddress::local(socket_path); auto socket_address_un = socket_address.to_sockaddr_un().release_value(); @@ -114,6 +126,7 @@ ErrorOr Process::create_ipc_socket(ByteString const& socket_path) return socket_fd; } +#endif ErrorOr Process::paths_for_process(StringView process_name) { diff --git a/Libraries/LibWebView/ProcessManager.cpp b/Libraries/LibWebView/ProcessManager.cpp index 8114cba2fd1..10e1b41c188 100644 --- a/Libraries/LibWebView/ProcessManager.cpp +++ b/Libraries/LibWebView/ProcessManager.cpp @@ -50,6 +50,8 @@ StringView process_name_from_type(ProcessType type) ProcessManager::ProcessManager() : on_process_exited([](Process&&) { }) { + // FIXME: Handle exiting child processes on Windows +#ifndef AK_OS_WINDOWS m_signal_handle = Core::EventLoop::register_signal(SIGCHLD, [this](int) { auto result = Core::System::waitpid(-1, WNOHANG); while (!result.is_error() && result.value().pid > 0) { @@ -61,6 +63,7 @@ ProcessManager::ProcessManager() result = Core::System::waitpid(-1, WNOHANG); } }); +#endif add_process(Process(WebView::ProcessType::Browser, nullptr, Core::Process::current())); @@ -74,7 +77,10 @@ ProcessManager::ProcessManager() ProcessManager::~ProcessManager() { + // FIXME: Handle exiting child processes on Windows +#ifndef AK_OS_WINDOWS Core::EventLoop::unregister_signal(m_signal_handle); +#endif } Optional ProcessManager::find_process(pid_t pid) diff --git a/Libraries/LibWebView/Utilities.cpp b/Libraries/LibWebView/Utilities.cpp index 9f5350cd25d..210b1e7bc66 100644 --- a/Libraries/LibWebView/Utilities.cpp +++ b/Libraries/LibWebView/Utilities.cpp @@ -104,7 +104,7 @@ ErrorOr> get_paths_for_helper_process(StringView process_name auto application_path = TRY(application_directory()); Vector paths; -#if !defined(AK_OS_MACOS) +#if !defined(AK_OS_MACOS) && !defined(AK_OS_WINDOWS) auto prefix = find_prefix(LexicalPath(application_path)); TRY(paths.try_append(LexicalPath::join(prefix.string(), libexec_path, process_name).string())); TRY(paths.try_append(LexicalPath::join(prefix.string(), "bin"sv, process_name).string())); From 1f8e7c3ccad4385277bf788ed0d9b2b99816091c Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 20 Mar 2025 00:44:06 +0100 Subject: [PATCH 095/141] LibWeb/Layout: Improve grid item sizing for replaced boxes With this change we no longer stretch "width: auto" for replaced elements and also use "width calculation rules for block-level replaced elements", like suggested by the spec. --- .../LibWeb/Layout/GridFormattingContext.cpp | 59 +++++++++++-------- .../Layout/expected/grid/replaced-item-2.txt | 11 ++++ .../Layout/input/grid/replaced-item-2.html | 12 ++++ ...grid-alignment-implies-size-change-013.txt | 5 +- ...grid-alignment-implies-size-change-014.txt | 5 +- ...grid-alignment-implies-size-change-015.txt | 5 +- ...grid-alignment-implies-size-change-016.txt | 5 +- ...grid-alignment-implies-size-change-031.txt | 5 +- ...grid-alignment-implies-size-change-032.txt | 4 +- ...grid-alignment-implies-size-change-033.txt | 5 +- ...grid-alignment-implies-size-change-034.txt | 5 +- .../grid-alignment-style-changes-005.txt | 8 +-- .../grid-alignment-style-changes-006.txt | 8 +-- 13 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/grid/replaced-item-2.txt create mode 100644 Tests/LibWeb/Layout/input/grid/replaced-item-2.html diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index bec973d9521..27c2d8beb40 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -1549,6 +1549,7 @@ void GridFormattingContext::resolve_grid_item_widths() { for (auto& item : m_grid_items) { CSSPixels containing_block_width = containing_block_size_for_item(item, GridDimension::Column); + CSS::JustifyItems justification = justification_for_item(item.box); auto const& computed_values = item.box->computed_values(); auto const& computed_width = computed_values.width(); @@ -1559,15 +1560,14 @@ void GridFormattingContext::resolve_grid_item_widths() CSSPixels width; }; - ItemAlignment initial { - .margin_left = item.used_values.margin_left, - .margin_right = item.used_values.margin_right, - .width = item.used_values.content_width() - }; + auto try_compute_width = [&item, containing_block_width, justification](CSSPixels a_width, CSS::Size const& computed_width) -> ItemAlignment { + auto const& computed_values = item.box->computed_values(); - auto try_compute_width = [&](CSSPixels a_width, CSS::Size const& computed_width) -> ItemAlignment { - ItemAlignment result = initial; - result.width = a_width; + ItemAlignment result = { + .margin_left = item.used_values.margin_left, + .margin_right = item.used_values.margin_right, + .width = a_width + }; // Auto margins absorb positive free space prior to alignment via the box alignment properties. auto free_space_left_for_margins = containing_block_width - result.width - item.used_values.border_left - item.used_values.border_right - item.used_values.padding_left - item.used_values.padding_right - item.used_values.margin_left - item.used_values.margin_right; @@ -1578,13 +1578,14 @@ void GridFormattingContext::resolve_grid_item_widths() result.margin_left = free_space_left_for_margins; } else if (computed_values.margin().right().is_auto()) { result.margin_right = free_space_left_for_margins; - } else if (computed_width.is_auto()) { + } else if (computed_width.is_auto() && !item.box->is_replaced_box()) { result.width += free_space_left_for_margins; } auto free_space_left_for_alignment = containing_block_width - a_width - item.used_values.border_left - item.used_values.border_right - item.used_values.padding_left - item.used_values.padding_right - item.used_values.margin_left - item.used_values.margin_right; - switch (justification_for_item(item.box)) { + switch (justification) { case CSS::JustifyItems::Normal: + break; case CSS::JustifyItems::Stretch: break; case CSS::JustifyItems::Center: @@ -1613,13 +1614,18 @@ void GridFormattingContext::resolve_grid_item_widths() ItemAlignment used_alignment; AvailableSpace available_space { AvailableSize::make_definite(containing_block_width), AvailableSize::make_indefinite() }; - if (computed_width.is_auto()) { - used_alignment = try_compute_width(calculate_fit_content_width(item.box, available_space), computed_width); - } else if (computed_width.is_fit_content()) { - used_alignment = try_compute_width(calculate_fit_content_width(item.box, available_space), computed_width); + if (item.box->is_replaced_box() && item.box->has_natural_width()) { + auto width = tentative_width_for_replaced_element(item.box, computed_values.width(), available_space); + used_alignment = try_compute_width(width, computed_width); } else { - auto width_px = calculate_inner_width(item.box, available_space.width, computed_width); - used_alignment = try_compute_width(width_px, computed_width); + if (computed_width.is_auto() || computed_width.is_fit_content()) { + auto fit_content_width = calculate_fit_content_width(item.box, available_space); + used_alignment = try_compute_width(fit_content_width, computed_width); + used_alignment = try_compute_width(calculate_fit_content_width(item.box, available_space), computed_width); + } else { + auto width_px = calculate_inner_width(item.box, available_space.width, computed_width); + used_alignment = try_compute_width(width_px, computed_width); + } } if (!should_treat_max_width_as_none(item.box, m_available_space->width)) { @@ -1648,6 +1654,7 @@ void GridFormattingContext::resolve_grid_item_heights() { for (auto& item : m_grid_items) { CSSPixels containing_block_height = containing_block_size_for_item(item, GridDimension::Row); + CSS::AlignItems alignment = alignment_for_item(item.box); auto const& computed_values = item.box->computed_values(); auto const& computed_height = computed_values.height(); @@ -1658,15 +1665,14 @@ void GridFormattingContext::resolve_grid_item_heights() CSSPixels height; }; - ItemAlignment initial { - .margin_top = item.used_values.margin_top, - .margin_bottom = item.used_values.margin_bottom, - .height = item.used_values.content_height() - }; + auto try_compute_height = [&item, containing_block_height, alignment](CSSPixels a_height) -> ItemAlignment { + auto const& computed_values = item.box->computed_values(); - auto try_compute_height = [&](CSSPixels a_height) -> ItemAlignment { - ItemAlignment result = initial; - result.height = a_height; + ItemAlignment result = { + .margin_top = item.used_values.margin_top, + .margin_bottom = item.used_values.margin_bottom, + .height = a_height + }; CSSPixels height = a_height; auto underflow_px = containing_block_height - height - item.used_values.border_top - item.used_values.border_bottom - item.used_values.padding_top - item.used_values.padding_bottom - item.used_values.margin_top - item.used_values.margin_bottom; @@ -1678,13 +1684,14 @@ void GridFormattingContext::resolve_grid_item_heights() result.margin_top = underflow_px; } else if (computed_values.margin().bottom().is_auto()) { result.margin_bottom = underflow_px; - } else if (computed_values.height().is_auto()) { + } else if (computed_values.height().is_auto() && !item.box->is_replaced_box()) { height += underflow_px; } - switch (alignment_for_item(item.box)) { + switch (alignment) { case CSS::AlignItems::Baseline: // FIXME: Not implemented + break; case CSS::AlignItems::Stretch: case CSS::AlignItems::Normal: result.height = height; diff --git a/Tests/LibWeb/Layout/expected/grid/replaced-item-2.txt b/Tests/LibWeb/Layout/expected/grid/replaced-item-2.txt new file mode 100644 index 00000000000..1eb67f9cbc8 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/grid/replaced-item-2.txt @@ -0,0 +1,11 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x116 [BFC] children: not-inline + BlockContainer at (8,8) content-size 100x100 children: not-inline + Box at (8,8) content-size 100x100 [GFC] children: not-inline + ImageBox at (8,8) content-size 10x10 children: not-inline + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x116] + PaintableWithLines (BlockContainer) [8,8 100x100] + PaintableBox (Box
.img-wrapper) [8,8 100x100] + ImagePaintable (ImageBox) [8,8 10x10] diff --git a/Tests/LibWeb/Layout/input/grid/replaced-item-2.html b/Tests/LibWeb/Layout/input/grid/replaced-item-2.html new file mode 100644 index 00000000000..b3fa756dcc7 --- /dev/null +++ b/Tests/LibWeb/Layout/input/grid/replaced-item-2.html @@ -0,0 +1,12 @@ +
\ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-013.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-013.txt index 0d45e002b62..9dd477389ba 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-013.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-013.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail +1 Pass +1 Fail Fail .before 1 -Fail .after 2 \ No newline at end of file +Pass .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-014.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-014.txt index 0d45e002b62..c2d337bbcc7 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-014.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-014.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail -Fail .before 1 +1 Pass +1 Fail +Pass .before 1 Fail .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-015.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-015.txt index c2d337bbcc7..438a9823e7d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-015.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-015.txt @@ -2,7 +2,6 @@ Harness status: OK Found 2 tests -1 Pass -1 Fail +2 Pass Pass .before 1 -Fail .after 2 \ No newline at end of file +Pass .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-016.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-016.txt index 9dd477389ba..438a9823e7d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-016.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-016.txt @@ -2,7 +2,6 @@ Harness status: OK Found 2 tests -1 Pass -1 Fail -Fail .before 1 +2 Pass +Pass .before 1 Pass .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-031.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-031.txt index 0d45e002b62..9dd477389ba 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-031.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-031.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail +1 Pass +1 Fail Fail .before 1 -Fail .after 2 \ No newline at end of file +Pass .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-032.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-032.txt index 9dd477389ba..c2d337bbcc7 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-032.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-032.txt @@ -4,5 +4,5 @@ Found 2 tests 1 Pass 1 Fail -Fail .before 1 -Pass .after 2 \ No newline at end of file +Pass .before 1 +Fail .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-033.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-033.txt index c2d337bbcc7..438a9823e7d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-033.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-033.txt @@ -2,7 +2,6 @@ Harness status: OK Found 2 tests -1 Pass -1 Fail +2 Pass Pass .before 1 -Fail .after 2 \ No newline at end of file +Pass .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-034.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-034.txt index 9dd477389ba..438a9823e7d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-034.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-implies-size-change-034.txt @@ -2,7 +2,6 @@ Harness status: OK Found 2 tests -1 Pass -1 Fail -Fail .before 1 +2 Pass +Pass .before 1 Pass .after 2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-005.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-005.txt index 7a6bdbab231..6afc4086d97 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-005.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-005.txt @@ -2,11 +2,11 @@ Harness status: OK Found 6 tests -1 Pass -5 Fail +3 Pass +3 Fail Fail .before 1 -Fail .before 2 +Pass .before 2 Fail .before 3 Fail .after 4 Pass .after 5 -Fail .after 6 \ No newline at end of file +Pass .after 6 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-006.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-006.txt index 9a1f4bbf93e..b5aca6fd478 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-006.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-grid/alignment/grid-alignment-style-changes-006.txt @@ -2,11 +2,11 @@ Harness status: OK Found 6 tests -1 Pass -5 Fail +3 Pass +3 Fail Fail .before 1 Pass .before 2 -Fail .before 3 +Pass .before 3 Fail .after 4 -Fail .after 5 +Pass .after 5 Fail .after 6 \ No newline at end of file From 19529590b9cd3f24196a64a368dbe87c038216b7 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 12:46:36 -0400 Subject: [PATCH 096/141] LibDevTools+WebContent: Simplify sending box-model properties a bit Now that we aren't piggy-backing on the Inspector interface, we can make our box-model serialization provide exactly the values that DevTools requires. --- .../LibDevTools/Actors/PageStyleActor.cpp | 58 +++++++++---------- Services/WebContent/ConnectionFromClient.cpp | 44 ++++++++------ 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.cpp b/Libraries/LibDevTools/Actors/PageStyleActor.cpp index 9adf529aa68..6600cc120b9 100644 --- a/Libraries/LibDevTools/Actors/PageStyleActor.cpp +++ b/Libraries/LibDevTools/Actors/PageStyleActor.cpp @@ -14,47 +14,47 @@ namespace DevTools { -static void received_layout(JsonObject& response, JsonObject const& computed_style, JsonObject const& node_box_sizing) +static void received_layout(JsonObject& response, JsonObject const& node_box_sizing) { response.set("autoMargins"sv, JsonObject {}); - auto pixel_value = [&](auto const& object, auto key) { - return object.get_double_with_precision_loss(key).value_or(0); + auto pixel_value = [&](auto key) { + return node_box_sizing.get_double_with_precision_loss(key).value_or(0); }; - auto set_pixel_value_from = [&](auto const& object, auto object_key, auto message_key) { - response.set(message_key, MUST(String::formatted("{}px", pixel_value(object, object_key)))); + auto set_pixel_value = [&](auto key) { + response.set(key, MUST(String::formatted("{}px", pixel_value(key)))); }; - auto set_computed_value_from = [&](auto const& object, auto key) { - response.set(key, object.get_string(key).value_or(String {})); + auto set_computed_value = [&](auto key) { + response.set(key, node_box_sizing.get_string(key).value_or(String {})); }; - response.set("width"sv, pixel_value(node_box_sizing, "content_width"sv)); - response.set("height"sv, pixel_value(node_box_sizing, "content_height"sv)); - // FIXME: This response should also contain "top", "right", "bottom", and "left", but our box model metrics in // WebContent do not provide this information. - set_pixel_value_from(node_box_sizing, "border_top"sv, "border-top-width"sv); - set_pixel_value_from(node_box_sizing, "border_right"sv, "border-right-width"sv); - set_pixel_value_from(node_box_sizing, "border_bottom"sv, "border-bottom-width"sv); - set_pixel_value_from(node_box_sizing, "border_left"sv, "border-left-width"sv); + set_computed_value("width"sv); + set_computed_value("height"sv); - set_pixel_value_from(node_box_sizing, "margin_top"sv, "margin-top"sv); - set_pixel_value_from(node_box_sizing, "margin_right"sv, "margin-right"sv); - set_pixel_value_from(node_box_sizing, "margin_bottom"sv, "margin-bottom"sv); - set_pixel_value_from(node_box_sizing, "margin_left"sv, "margin-left"sv); + set_pixel_value("border-top-width"sv); + set_pixel_value("border-right-width"sv); + set_pixel_value("border-bottom-width"sv); + set_pixel_value("border-left-width"sv); - set_pixel_value_from(node_box_sizing, "padding_top"sv, "padding-top"sv); - set_pixel_value_from(node_box_sizing, "padding_right"sv, "padding-right"sv); - set_pixel_value_from(node_box_sizing, "padding_bottom"sv, "padding-bottom"sv); - set_pixel_value_from(node_box_sizing, "padding_left"sv, "padding-left"sv); + set_pixel_value("margin-top"sv); + set_pixel_value("margin-right"sv); + set_pixel_value("margin-bottom"sv); + set_pixel_value("margin-left"sv); - set_computed_value_from(computed_style, "box-sizing"sv); - set_computed_value_from(computed_style, "display"sv); - set_computed_value_from(computed_style, "float"sv); - set_computed_value_from(computed_style, "line-height"sv); - set_computed_value_from(computed_style, "position"sv); - set_computed_value_from(computed_style, "z-index"sv); + set_pixel_value("padding-top"sv); + set_pixel_value("padding-right"sv); + set_pixel_value("padding-bottom"sv); + set_pixel_value("padding-left"sv); + + set_computed_value("box-sizing"sv); + set_computed_value("display"sv); + set_computed_value("float"sv); + set_computed_value("line-height"sv); + set_computed_value("position"sv); + set_computed_value("z-index"sv); } static void received_computed_style(JsonObject& response, JsonObject const& computed_style) @@ -151,7 +151,7 @@ void PageStyleActor::handle_message(Message const& message) return; inspect_dom_node(message, *node, [](auto& response, auto const& properties) { - received_layout(response, properties.computed_style, properties.node_box_sizing); + received_layout(response, properties.node_box_sizing); }); return; diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index f8c1a510acb..ab1ca3ec574 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -504,28 +504,38 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_ return MUST(builder.to_string()); }; - auto serialize_node_box_sizing_json = [](Web::Layout::Node const* layout_node) { + auto serialize_node_box_sizing_json = [](Web::Layout::Node const* layout_node, GC::Ptr properties) { if (!layout_node || !layout_node->is_box() || !layout_node->first_paintable() || !layout_node->first_paintable()->is_paintable_box()) { return "{}"_string; } auto const& paintable_box = as(*layout_node->first_paintable()); auto const& box_model = paintable_box.box_model(); StringBuilder builder; + auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - MUST(serializer.add("padding_top"sv, box_model.padding.top.to_double())); - MUST(serializer.add("padding_right"sv, box_model.padding.right.to_double())); - MUST(serializer.add("padding_bottom"sv, box_model.padding.bottom.to_double())); - MUST(serializer.add("padding_left"sv, box_model.padding.left.to_double())); - MUST(serializer.add("margin_top"sv, box_model.margin.top.to_double())); - MUST(serializer.add("margin_right"sv, box_model.margin.right.to_double())); - MUST(serializer.add("margin_bottom"sv, box_model.margin.bottom.to_double())); - MUST(serializer.add("margin_left"sv, box_model.margin.left.to_double())); - MUST(serializer.add("border_top"sv, box_model.border.top.to_double())); - MUST(serializer.add("border_right"sv, box_model.border.right.to_double())); - MUST(serializer.add("border_bottom"sv, box_model.border.bottom.to_double())); - MUST(serializer.add("border_left"sv, box_model.border.left.to_double())); - MUST(serializer.add("content_width"sv, paintable_box.content_width().to_double())); - MUST(serializer.add("content_height"sv, paintable_box.content_height().to_double())); + MUST(serializer.add("width"sv, paintable_box.content_width().to_double())); + MUST(serializer.add("height"sv, paintable_box.content_height().to_double())); + MUST(serializer.add("padding-top"sv, box_model.padding.top.to_double())); + MUST(serializer.add("padding-right"sv, box_model.padding.right.to_double())); + MUST(serializer.add("padding-bottom"sv, box_model.padding.bottom.to_double())); + MUST(serializer.add("padding-left"sv, box_model.padding.left.to_double())); + MUST(serializer.add("margin-top"sv, box_model.margin.top.to_double())); + MUST(serializer.add("margin-right"sv, box_model.margin.right.to_double())); + MUST(serializer.add("margin-bottom"sv, box_model.margin.bottom.to_double())); + MUST(serializer.add("margin-left"sv, box_model.margin.left.to_double())); + MUST(serializer.add("border-top-width"sv, box_model.border.top.to_double())); + MUST(serializer.add("border-right-width"sv, box_model.border.right.to_double())); + MUST(serializer.add("border-bottom-width"sv, box_model.border.bottom.to_double())); + MUST(serializer.add("border-left-width"sv, box_model.border.left.to_double())); + + if (properties) { + MUST(serializer.add("box-sizing"sv, properties->property(Web::CSS::PropertyID::BoxSizing).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); + MUST(serializer.add("display"sv, properties->property(Web::CSS::PropertyID::Display).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); + MUST(serializer.add("float"sv, properties->property(Web::CSS::PropertyID::Float).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); + MUST(serializer.add("line-height"sv, properties->property(Web::CSS::PropertyID::LineHeight).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); + MUST(serializer.add("position"sv, properties->property(Web::CSS::PropertyID::Position).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); + MUST(serializer.add("z-index"sv, properties->property(Web::CSS::PropertyID::ZIndex).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); + } MUST(serializer.finish()); return MUST(builder.to_string()); @@ -584,7 +594,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_ } auto custom_properties_json = serialize_custom_properties_json(element, pseudo_element); - auto node_box_sizing_json = serialize_node_box_sizing_json(pseudo_element_node.ptr()); + auto node_box_sizing_json = serialize_node_box_sizing_json(pseudo_element_node.ptr(), pseudo_element_style); async_did_inspect_dom_node(page_id, true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json, "{}"_string, fonts_json); return; } @@ -592,7 +602,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_ auto computed_values = serialize_json(*element.computed_properties()); auto resolved_values = serialize_json(element.resolved_css_values()); auto custom_properties_json = serialize_custom_properties_json(element, {}); - auto node_box_sizing_json = serialize_node_box_sizing_json(element.layout_node()); + auto node_box_sizing_json = serialize_node_box_sizing_json(element.layout_node(), element.computed_properties()); auto aria_properties_state_json = serialize_aria_properties_state_json(element); auto fonts_json = serialize_fonts_json(*element.computed_properties()); From daca9f599522994caf7866eb72722de9366bd4b5 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 15:50:56 -0400 Subject: [PATCH 097/141] LibDevTools+LibWebView+WebContent: Selectively fetch DOM node properties When we inspect a DOM node, we currently serialize many properties for that node, including its layout, computed style, used fonts, etc. Now that we aren't piggy-backing on the Inspector interface, we can instead only serialize the specific information required by DevTools. --- Libraries/LibDevTools/Actors/FrameActor.cpp | 1 + .../LibDevTools/Actors/PageStyleActor.cpp | 85 +++--- Libraries/LibDevTools/Actors/PageStyleActor.h | 15 +- Libraries/LibDevTools/DevToolsDelegate.h | 7 +- Libraries/LibWebView/Application.cpp | 32 +- Libraries/LibWebView/Application.h | 4 +- Libraries/LibWebView/CMakeLists.txt | 1 + Libraries/LibWebView/DOMNodeProperties.cpp | 26 ++ Libraries/LibWebView/DOMNodeProperties.h | 35 +++ Libraries/LibWebView/Forward.h | 1 + Libraries/LibWebView/ViewImplementation.cpp | 6 +- Libraries/LibWebView/ViewImplementation.h | 13 +- Libraries/LibWebView/WebContentClient.cpp | 46 +-- Libraries/LibWebView/WebContentClient.h | 2 +- Services/WebContent/ConnectionFromClient.cpp | 278 ++++++++---------- Services/WebContent/ConnectionFromClient.h | 4 +- Services/WebContent/WebContentClient.ipc | 3 +- Services/WebContent/WebContentServer.ipc | 4 +- 18 files changed, 287 insertions(+), 276 deletions(-) create mode 100644 Libraries/LibWebView/DOMNodeProperties.cpp create mode 100644 Libraries/LibWebView/DOMNodeProperties.h diff --git a/Libraries/LibDevTools/Actors/FrameActor.cpp b/Libraries/LibDevTools/Actors/FrameActor.cpp index e413386d048..2fe33be7690 100644 --- a/Libraries/LibDevTools/Actors/FrameActor.cpp +++ b/Libraries/LibDevTools/Actors/FrameActor.cpp @@ -66,6 +66,7 @@ void FrameActor::handle_message(Message const& message) if (message.type == "detach"sv) { if (auto tab = m_tab.strong_ref()) { + devtools().delegate().stop_listening_for_dom_properties(tab->description()); devtools().delegate().stop_listening_for_dom_mutations(tab->description()); devtools().delegate().stop_listening_for_console_messages(tab->description()); devtools().delegate().stop_listening_for_style_sheet_sources(tab->description()); diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.cpp b/Libraries/LibDevTools/Actors/PageStyleActor.cpp index 6600cc120b9..7db023d5cfc 100644 --- a/Libraries/LibDevTools/Actors/PageStyleActor.cpp +++ b/Libraries/LibDevTools/Actors/PageStyleActor.cpp @@ -4,6 +4,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include @@ -111,9 +113,20 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtrdescription(), + [weak_self = make_weak_ptr()](WebView::DOMNodeProperties const& properties) { + if (auto self = weak_self.strong_ref()) + self->received_dom_node_properties(properties); + }); + } } -PageStyleActor::~PageStyleActor() = default; +PageStyleActor::~PageStyleActor() +{ + if (auto tab = InspectorActor::tab_for(m_inspector)) + devtools().delegate().stop_listening_for_dom_properties(tab->description()); +} void PageStyleActor::handle_message(Message const& message) { @@ -134,38 +147,17 @@ void PageStyleActor::handle_message(Message const& message) } if (message.type == "getComputed"sv) { - auto node = get_required_parameter(message, "node"sv); - if (!node.has_value()) - return; - - inspect_dom_node(message, *node, [](auto& response, auto const& properties) { - received_computed_style(response, properties.computed_style); - }); - + inspect_dom_node(message, WebView::DOMNodeProperties::Type::ComputedStyle); return; } if (message.type == "getLayout"sv) { - auto node = get_required_parameter(message, "node"sv); - if (!node.has_value()) - return; - - inspect_dom_node(message, *node, [](auto& response, auto const& properties) { - received_layout(response, properties.node_box_sizing); - }); - + inspect_dom_node(message, WebView::DOMNodeProperties::Type::Layout); return; } if (message.type == "getUsedFontFaces"sv) { - auto node = get_required_parameter(message, "node"sv); - if (!node.has_value()) - return; - - inspect_dom_node(message, *node, [](auto& response, auto const& properties) { - received_fonts(response, properties.fonts); - }); - + inspect_dom_node(message, WebView::DOMNodeProperties::Type::UsedFonts); return; } @@ -192,19 +184,46 @@ JsonValue PageStyleActor::serialize_style() const return style; } -template -void PageStyleActor::inspect_dom_node(Message const& message, StringView node_actor, Callback&& callback) +void PageStyleActor::inspect_dom_node(Message const& message, WebView::DOMNodeProperties::Type property_type) { - auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), node_actor); + auto node = get_required_parameter(message, "node"sv); + if (!node.has_value()) + return; + + auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), *node); if (!dom_node.has_value()) { - send_unknown_actor_error(message, node_actor); + send_unknown_actor_error(message, *node); return; } - devtools().delegate().inspect_dom_node(dom_node->tab->description(), dom_node->identifier.id, dom_node->identifier.pseudo_element, - async_handler(message, [callback = forward(callback)](auto&, auto properties, auto& response) { - callback(response, properties); - })); + devtools().delegate().inspect_dom_node(dom_node->tab->description(), property_type, dom_node->identifier.id, dom_node->identifier.pseudo_element); + m_pending_inspect_requests.append({ .id = message.id }); +} + +void PageStyleActor::received_dom_node_properties(WebView::DOMNodeProperties const& properties) +{ + if (m_pending_inspect_requests.is_empty()) + return; + + JsonObject response; + + switch (properties.type) { + case WebView::DOMNodeProperties::Type::ComputedStyle: + if (properties.properties.is_object()) + received_computed_style(response, properties.properties.as_object()); + break; + case WebView::DOMNodeProperties::Type::Layout: + if (properties.properties.is_object()) + received_layout(response, properties.properties.as_object()); + break; + case WebView::DOMNodeProperties::Type::UsedFonts: + if (properties.properties.is_array()) + received_fonts(response, properties.properties.as_array()); + break; + } + + auto message = m_pending_inspect_requests.take_first(); + send_response(message, move(response)); } } diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.h b/Libraries/LibDevTools/Actors/PageStyleActor.h index 54a9e70edc3..469e44558b2 100644 --- a/Libraries/LibDevTools/Actors/PageStyleActor.h +++ b/Libraries/LibDevTools/Actors/PageStyleActor.h @@ -6,19 +6,12 @@ #pragma once -#include -#include #include #include +#include namespace DevTools { -struct DOMNodeProperties { - JsonObject computed_style; - JsonObject node_box_sizing; - JsonArray fonts; -}; - class PageStyleActor final : public Actor { public: static constexpr auto base_name = "page-style"sv; @@ -33,10 +26,12 @@ private: virtual void handle_message(Message const&) override; - template - void inspect_dom_node(Message const&, StringView node_actor, Callback&&); + void inspect_dom_node(Message const&, WebView::DOMNodeProperties::Type); + void received_dom_node_properties(WebView::DOMNodeProperties const&); WeakPtr m_inspector; + + Vector m_pending_inspect_requests; }; } diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index a050087bc63..380019008b4 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace DevTools { @@ -31,8 +32,10 @@ public: using OnTabInspectionComplete = Function)>; virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { } - using OnDOMNodeInspectionComplete = Function)>; - virtual void inspect_dom_node(TabDescription const&, Web::UniqueNodeID, Optional, OnDOMNodeInspectionComplete) const { } + using OnDOMNodePropertiesReceived = Function; + virtual void listen_for_dom_properties(TabDescription const&, OnDOMNodePropertiesReceived) const { } + virtual void stop_listening_for_dom_properties(TabDescription const&) const { } + virtual void inspect_dom_node(TabDescription const&, WebView::DOMNodeProperties::Type, Web::UniqueNodeID, Optional) const { } virtual void clear_inspected_dom_node(TabDescription const&) const { } virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional) const { } diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index cd866dfc055..cd6871d1170 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -451,25 +451,31 @@ void Application::inspect_tab(DevTools::TabDescription const& description, OnTab view->inspect_dom_tree(); } -void Application::inspect_dom_node(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, Optional pseudo_element, OnDOMNodeInspectionComplete on_complete) const +void Application::listen_for_dom_properties(DevTools::TabDescription const& description, OnDOMNodePropertiesReceived on_dom_node_properties_received) const { auto view = ViewImplementation::find_view_by_id(description.id); - if (!view.has_value()) { - on_complete(Error::from_string_literal("Unable to locate tab")); + if (!view.has_value()) return; - } - view->on_received_dom_node_properties = [&view = *view, on_complete = move(on_complete)](ViewImplementation::DOMNodeProperties properties) { - view.on_received_dom_node_properties = nullptr; + view->on_received_dom_node_properties = move(on_dom_node_properties_received); +} - on_complete(DevTools::DOMNodeProperties { - .computed_style = move(properties.computed_style), - .node_box_sizing = move(properties.node_box_sizing), - .fonts = move(properties.fonts), - }); - }; +void Application::stop_listening_for_dom_properties(DevTools::TabDescription const& description) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) + return; - view->inspect_dom_node(node_id, pseudo_element); + view->on_received_dom_node_properties = nullptr; +} + +void Application::inspect_dom_node(DevTools::TabDescription const& description, DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional pseudo_element) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) + return; + + view->inspect_dom_node(node_id, property_type, pseudo_element); } void Application::clear_inspected_dom_node(DevTools::TabDescription const& description) const diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 055c78669c5..b51f35c2cf6 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -97,7 +97,9 @@ private: virtual Vector tab_list() const override; virtual Vector css_property_list() const override; virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete) const override; - virtual void inspect_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional, OnDOMNodeInspectionComplete) const override; + virtual void listen_for_dom_properties(DevTools::TabDescription const&, OnDOMNodePropertiesReceived) const override; + virtual void stop_listening_for_dom_properties(DevTools::TabDescription const&) const override; + virtual void inspect_dom_node(DevTools::TabDescription const&, DOMNodeProperties::Type, Web::UniqueNodeID, Optional) const override; virtual void clear_inspected_dom_node(DevTools::TabDescription const&) const override; virtual void highlight_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional) const override; virtual void clear_highlighted_dom_node(DevTools::TabDescription const&) const override; diff --git a/Libraries/LibWebView/CMakeLists.txt b/Libraries/LibWebView/CMakeLists.txt index a9476ec7276..f628ab8fbf9 100644 --- a/Libraries/LibWebView/CMakeLists.txt +++ b/Libraries/LibWebView/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES ConsoleOutput.cpp CookieJar.cpp Database.cpp + DOMNodeProperties.cpp HelperProcess.cpp Mutation.cpp Plugins/FontPlugin.cpp diff --git a/Libraries/LibWebView/DOMNodeProperties.cpp b/Libraries/LibWebView/DOMNodeProperties.cpp new file mode 100644 index 00000000000..1389e37d5a2 --- /dev/null +++ b/Libraries/LibWebView/DOMNodeProperties.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +template<> +ErrorOr IPC::encode(Encoder& encoder, WebView::DOMNodeProperties const& attribute) +{ + TRY(encoder.encode(attribute.type)); + TRY(encoder.encode(attribute.properties)); + return {}; +} + +template<> +ErrorOr IPC::decode(Decoder& decoder) +{ + auto type = TRY(decoder.decode()); + auto properties = TRY(decoder.decode()); + + return WebView::DOMNodeProperties { type, move(properties) }; +} diff --git a/Libraries/LibWebView/DOMNodeProperties.h b/Libraries/LibWebView/DOMNodeProperties.h new file mode 100644 index 00000000000..ae71db6db1b --- /dev/null +++ b/Libraries/LibWebView/DOMNodeProperties.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace WebView { + +struct DOMNodeProperties { + enum class Type { + ComputedStyle, + Layout, + UsedFonts, + }; + + Type type { Type::ComputedStyle }; + JsonValue properties; +}; + +} + +namespace IPC { + +template<> +ErrorOr encode(Encoder&, WebView::DOMNodeProperties const&); + +template<> +ErrorOr decode(Decoder&); + +} diff --git a/Libraries/LibWebView/Forward.h b/Libraries/LibWebView/Forward.h index f5d98001318..2056ee0d205 100644 --- a/Libraries/LibWebView/Forward.h +++ b/Libraries/LibWebView/Forward.h @@ -21,6 +21,7 @@ class WebContentClient; struct Attribute; struct ConsoleOutput; struct CookieStorageKey; +struct DOMNodeProperties; struct Mutation; struct ProcessHandle; struct SearchEngine; diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index 41113bef978..e90c4fd1f22 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -329,14 +329,14 @@ void ViewImplementation::get_hovered_node_id() client().async_get_hovered_node_id(page_id()); } -void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element) +void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, DOMNodeProperties::Type property_type, Optional pseudo_element) { - client().async_inspect_dom_node(page_id(), node_id, pseudo_element); + client().async_inspect_dom_node(page_id(), property_type, node_id, pseudo_element); } void ViewImplementation::clear_inspected_dom_node() { - inspect_dom_node(0, {}); + client().async_clear_inspected_dom_node(page_id()); } void ViewImplementation::highlight_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element) diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index 0fcfbebd140..3c58e1979f0 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -36,15 +36,6 @@ class ViewImplementation { public: virtual ~ViewImplementation(); - struct DOMNodeProperties { - JsonObject computed_style; - JsonObject resolved_style; - JsonObject custom_properties; - JsonObject node_box_sizing; - JsonObject aria_properties_state; - JsonArray fonts; - }; - static void for_each_view(Function); static Optional find_view_by_id(u64); @@ -108,7 +99,7 @@ public: void inspect_accessibility_tree(); void get_hovered_node_id(); - void inspect_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); + void inspect_dom_node(Web::UniqueNodeID node_id, DOMNodeProperties::Type, Optional pseudo_element); void clear_inspected_dom_node(); void highlight_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index 462f382e9eb..d822bc4e88d 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -277,8 +277,7 @@ void WebContentClient::did_get_source(u64 page_id, URL::URL url, URL::URL base_u } } -template -static JsonType parse_json(StringView json, StringView name) +static JsonObject parse_json(StringView json, StringView name) { auto parsed_tree = JsonValue::from_string(json); if (parsed_tree.is_error()) { @@ -286,23 +285,12 @@ static JsonType parse_json(StringView json, StringView name) return {}; } - if constexpr (IsSame) { - if (!parsed_tree.value().is_object()) { - dbgln("Expected {} to be an object: {}", name, parsed_tree.value()); - return {}; - } - - return move(parsed_tree.release_value().as_object()); - } else if constexpr (IsSame) { - if (!parsed_tree.value().is_array()) { - dbgln("Expected {} to be an array: {}", name, parsed_tree.value()); - return {}; - } - - return move(parsed_tree.release_value().as_array()); - } else { - static_assert(DependentFalse); + if (!parsed_tree.value().is_object()) { + dbgln("Expected {} to be an object: {}", name, parsed_tree.value()); + return {}; } + + return move(parsed_tree.release_value().as_object()); } void WebContentClient::did_inspect_dom_tree(u64 page_id, String dom_tree) @@ -313,26 +301,12 @@ void WebContentClient::did_inspect_dom_tree(u64 page_id, String dom_tree) } } -void WebContentClient::did_inspect_dom_node(u64 page_id, bool has_style, String computed_style, String resolved_style, String custom_properties, String node_box_sizing, String aria_properties_state, String fonts) +void WebContentClient::did_inspect_dom_node(u64 page_id, DOMNodeProperties properties) { - auto view = view_for_page_id(page_id); - if (!view.has_value() || !view->on_received_dom_node_properties) - return; - - ViewImplementation::DOMNodeProperties properties; - - if (has_style) { - properties = ViewImplementation::DOMNodeProperties { - .computed_style = parse_json(computed_style, "computed style"sv), - .resolved_style = parse_json(resolved_style, "resolved style"sv), - .custom_properties = parse_json(custom_properties, "custom properties"sv), - .node_box_sizing = parse_json(node_box_sizing, "node box sizing"sv), - .aria_properties_state = parse_json(aria_properties_state, "aria properties state"sv), - .fonts = parse_json(fonts, "fonts"sv), - }; + if (auto view = view_for_page_id(page_id); view.has_value()) { + if (view->on_received_dom_node_properties) + view->on_received_dom_node_properties(move(properties)); } - - view->on_received_dom_node_properties(move(properties)); } void WebContentClient::did_inspect_accessibility_tree(u64 page_id, String accessibility_tree) diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index d01b5148c27..205f433c2ea 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -77,7 +77,7 @@ private: virtual void did_request_media_context_menu(u64 page_id, Gfx::IntPoint, ByteString, unsigned, Web::Page::MediaContextMenu) override; virtual void did_get_source(u64 page_id, URL::URL, URL::URL, String) override; virtual void did_inspect_dom_tree(u64 page_id, String) override; - virtual void did_inspect_dom_node(u64 page_id, bool has_style, String computed_style, String resolved_style, String custom_properties, String node_box_sizing, String aria_properties_state, String fonts) override; + virtual void did_inspect_dom_node(u64 page_id, DOMNodeProperties) override; virtual void did_inspect_accessibility_tree(u64 page_id, String) override; virtual void did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id) override; virtual void did_finish_editing_dom_node(u64 page_id, Optional node_id) override; diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index ab1ca3ec574..8c9638e2fcf 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -438,7 +438,121 @@ void ConnectionFromClient::inspect_dom_tree(u64 page_id) } } -void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) +void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional pseudo_element) +{ + auto page = this->page(page_id); + if (!page.has_value()) + return; + + clear_inspected_dom_node(page_id); + + auto* node = Web::DOM::Node::from_unique_id(node_id); + // Nodes without layout (aka non-visible nodes) don't have style computed. + if (!node || !node->layout_node() || !node->is_element()) { + async_did_inspect_dom_node(page_id, { property_type, {} }); + return; + } + + auto& element = as(*node); + node->document().set_inspected_node(node); + + GC::Ptr properties; + if (pseudo_element.has_value()) { + if (auto pseudo_element_node = element.get_pseudo_element_node(*pseudo_element)) + properties = element.pseudo_element_computed_properties(*pseudo_element); + } else { + properties = element.computed_properties(); + } + + if (!properties) { + async_did_inspect_dom_node(page_id, { property_type, {} }); + return; + } + + auto serialize_computed_style = [&]() { + JsonObject serialized; + + properties->for_each_property([&](auto property_id, auto& value) { + serialized.set( + Web::CSS::string_from_property_id(property_id), + value.to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + }); + + return serialized; + }; + + auto serialize_layout = [&](Web::Layout::Node const* layout_node) { + if (!layout_node || !layout_node->is_box() || !layout_node->first_paintable() || !layout_node->first_paintable()->is_paintable_box()) { + return JsonObject {}; + } + + auto const& paintable_box = as(*layout_node->first_paintable()); + auto const& box_model = paintable_box.box_model(); + + JsonObject serialized; + + serialized.set("width"sv, paintable_box.content_width().to_double()); + serialized.set("height"sv, paintable_box.content_height().to_double()); + + serialized.set("padding-top"sv, box_model.padding.top.to_double()); + serialized.set("padding-right"sv, box_model.padding.right.to_double()); + serialized.set("padding-bottom"sv, box_model.padding.bottom.to_double()); + serialized.set("padding-left"sv, box_model.padding.left.to_double()); + + serialized.set("margin-top"sv, box_model.margin.top.to_double()); + serialized.set("margin-right"sv, box_model.margin.right.to_double()); + serialized.set("margin-bottom"sv, box_model.margin.bottom.to_double()); + serialized.set("margin-left"sv, box_model.margin.left.to_double()); + + serialized.set("border-top-width"sv, box_model.border.top.to_double()); + serialized.set("border-right-width"sv, box_model.border.right.to_double()); + serialized.set("border-bottom-width"sv, box_model.border.bottom.to_double()); + serialized.set("border-left-width"sv, box_model.border.left.to_double()); + + serialized.set("box-sizing"sv, properties->property(Web::CSS::PropertyID::BoxSizing).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + serialized.set("display"sv, properties->property(Web::CSS::PropertyID::Display).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + serialized.set("float"sv, properties->property(Web::CSS::PropertyID::Float).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + serialized.set("line-height"sv, properties->property(Web::CSS::PropertyID::LineHeight).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + serialized.set("position"sv, properties->property(Web::CSS::PropertyID::Position).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + serialized.set("z-index"sv, properties->property(Web::CSS::PropertyID::ZIndex).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)); + + return serialized; + }; + + auto serialize_used_fonts = [&]() { + JsonArray serialized; + + properties->computed_font_list().for_each_font_entry([&](Gfx::FontCascadeList::Entry const& entry) { + auto const& font = *entry.font; + + JsonObject font_object; + font_object.set("name"sv, font.family().to_string()); + font_object.set("size"sv, font.point_size()); + font_object.set("weight"sv, font.weight()); + serialized.must_append(move(font_object)); + }); + + return serialized; + }; + + JsonValue serialized; + + switch (property_type) { + case WebView::DOMNodeProperties::Type::ComputedStyle: + serialized = serialize_computed_style(); + break; + case WebView::DOMNodeProperties::Type::Layout: + serialized = serialize_layout(element.layout_node()); + break; + case WebView::DOMNodeProperties::Type::UsedFonts: + serialized = serialize_used_fonts(); + break; + } + + async_did_inspect_dom_node(page_id, { property_type, move(serialized) }); +} + +void ConnectionFromClient::clear_inspected_dom_node(u64 page_id) { auto page = this->page(page_id); if (!page.has_value()) @@ -449,168 +563,6 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_ navigable->active_document()->set_inspected_node(nullptr); } } - - auto* node = Web::DOM::Node::from_unique_id(node_id); - // Note: Nodes without layout (aka non-visible nodes, don't have style computed) - if (!node || !node->layout_node()) { - async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); - return; - } - - node->document().set_inspected_node(node); - - if (node->is_element()) { - auto& element = as(*node); - if (!element.computed_properties()) { - async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); - return; - } - - auto serialize_json = [](Web::CSS::ComputedProperties const& properties) { - StringBuilder builder; - - auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - properties.for_each_property([&](auto property_id, auto& value) { - MUST(serializer.add(Web::CSS::string_from_property_id(property_id), value.to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal).to_byte_string())); - }); - MUST(serializer.finish()); - - return MUST(builder.to_string()); - }; - - auto serialize_custom_properties_json = [](Web::DOM::Element const& element, Optional pseudo_element) { - StringBuilder builder; - auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - HashTable seen_properties; - - auto const* element_to_check = &element; - auto pseudo_element_to_check = pseudo_element; - while (element_to_check) { - for (auto const& property : element_to_check->custom_properties(pseudo_element_to_check)) { - if (!seen_properties.contains(property.key)) { - seen_properties.set(property.key); - MUST(serializer.add(property.key, property.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - } - } - - if (pseudo_element_to_check.has_value()) { - pseudo_element_to_check.clear(); - } else { - element_to_check = element_to_check->parent_element(); - } - } - - MUST(serializer.finish()); - return MUST(builder.to_string()); - }; - - auto serialize_node_box_sizing_json = [](Web::Layout::Node const* layout_node, GC::Ptr properties) { - if (!layout_node || !layout_node->is_box() || !layout_node->first_paintable() || !layout_node->first_paintable()->is_paintable_box()) { - return "{}"_string; - } - auto const& paintable_box = as(*layout_node->first_paintable()); - auto const& box_model = paintable_box.box_model(); - StringBuilder builder; - - auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - MUST(serializer.add("width"sv, paintable_box.content_width().to_double())); - MUST(serializer.add("height"sv, paintable_box.content_height().to_double())); - MUST(serializer.add("padding-top"sv, box_model.padding.top.to_double())); - MUST(serializer.add("padding-right"sv, box_model.padding.right.to_double())); - MUST(serializer.add("padding-bottom"sv, box_model.padding.bottom.to_double())); - MUST(serializer.add("padding-left"sv, box_model.padding.left.to_double())); - MUST(serializer.add("margin-top"sv, box_model.margin.top.to_double())); - MUST(serializer.add("margin-right"sv, box_model.margin.right.to_double())); - MUST(serializer.add("margin-bottom"sv, box_model.margin.bottom.to_double())); - MUST(serializer.add("margin-left"sv, box_model.margin.left.to_double())); - MUST(serializer.add("border-top-width"sv, box_model.border.top.to_double())); - MUST(serializer.add("border-right-width"sv, box_model.border.right.to_double())); - MUST(serializer.add("border-bottom-width"sv, box_model.border.bottom.to_double())); - MUST(serializer.add("border-left-width"sv, box_model.border.left.to_double())); - - if (properties) { - MUST(serializer.add("box-sizing"sv, properties->property(Web::CSS::PropertyID::BoxSizing).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - MUST(serializer.add("display"sv, properties->property(Web::CSS::PropertyID::Display).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - MUST(serializer.add("float"sv, properties->property(Web::CSS::PropertyID::Float).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - MUST(serializer.add("line-height"sv, properties->property(Web::CSS::PropertyID::LineHeight).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - MUST(serializer.add("position"sv, properties->property(Web::CSS::PropertyID::Position).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - MUST(serializer.add("z-index"sv, properties->property(Web::CSS::PropertyID::ZIndex).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal))); - } - - MUST(serializer.finish()); - return MUST(builder.to_string()); - }; - - auto serialize_aria_properties_state_json = [](Web::DOM::Element const& element) { - auto role_name = element.role_or_default(); - if (!role_name.has_value()) { - return "{}"_string; - } - auto aria_data = MUST(Web::ARIA::AriaData::build_data(element)); - auto role = MUST(Web::ARIA::RoleType::build_role_object(role_name.value(), element.is_focusable(), *aria_data)); - - StringBuilder builder; - auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - MUST(role->serialize_as_json(serializer)); - MUST(serializer.finish()); - return MUST(builder.to_string()); - }; - - auto serialize_fonts_json = [](Web::CSS::ComputedProperties const& properties) { - StringBuilder builder; - auto serializer = MUST(JsonArraySerializer<>::try_create(builder)); - - auto const& font_list = properties.computed_font_list(); - font_list.for_each_font_entry([&serializer](Gfx::FontCascadeList::Entry const& entry) { - auto const& font = entry.font; - auto font_json_object = MUST(serializer.add_object()); - MUST(font_json_object.add("name"sv, font->family())); - MUST(font_json_object.add("size"sv, font->point_size())); - MUST(font_json_object.add("weight"sv, font->weight())); - MUST(font_json_object.finish()); - }); - MUST(serializer.finish()); - return MUST(builder.to_string()); - }; - - if (pseudo_element.has_value()) { - auto pseudo_element_node = element.get_pseudo_element_node(pseudo_element.value()); - if (!pseudo_element_node) { - async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); - return; - } - - auto pseudo_element_style = element.pseudo_element_computed_properties(pseudo_element.value()); - - String computed_values; - String fonts_json; - String resolved_values; - if (pseudo_element_style) { - computed_values = serialize_json(*pseudo_element_style); - fonts_json = serialize_fonts_json(*pseudo_element_style); - resolved_values = serialize_json(element.resolved_css_values(pseudo_element.value())); - } else { - dbgln("Inspected pseudo-element has no computed style."); - } - - auto custom_properties_json = serialize_custom_properties_json(element, pseudo_element); - auto node_box_sizing_json = serialize_node_box_sizing_json(pseudo_element_node.ptr(), pseudo_element_style); - async_did_inspect_dom_node(page_id, true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json, "{}"_string, fonts_json); - return; - } - - auto computed_values = serialize_json(*element.computed_properties()); - auto resolved_values = serialize_json(element.resolved_css_values()); - auto custom_properties_json = serialize_custom_properties_json(element, {}); - auto node_box_sizing_json = serialize_node_box_sizing_json(element.layout_node(), element.computed_properties()); - auto aria_properties_state_json = serialize_aria_properties_state_json(element); - auto fonts_json = serialize_fonts_json(*element.computed_properties()); - - async_did_inspect_dom_node(page_id, true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json, aria_properties_state_json, move(fonts_json)); - return; - } - - async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); } void ConnectionFromClient::highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 33688ecae18..3e6d8c55890 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -75,7 +76,8 @@ private: virtual void debug_request(u64 page_id, ByteString, ByteString) override; virtual void get_source(u64 page_id) override; virtual void inspect_dom_tree(u64 page_id) override; - virtual void inspect_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) override; + virtual void inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type, Web::UniqueNodeID node_id, Optional pseudo_element) override; + virtual void clear_inspected_dom_node(u64 page_id) override; virtual void highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) override; virtual void inspect_accessibility_tree(u64 page_id) override; virtual void get_hovered_node_id(u64 page_id) override; diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index b7b6d076b33..adfc6bbf2fb 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,7 @@ endpoint WebContentClient did_get_source(u64 page_id, URL::URL url, URL::URL base_url, String source) =| did_inspect_dom_tree(u64 page_id, String dom_tree) =| - did_inspect_dom_node(u64 page_id, bool has_style, String computed_style, String resolved_style, String custom_properties, String node_box_sizing, String aria_properties_state, String fonts) =| + did_inspect_dom_node(u64 page_id, WebView::DOMNodeProperties properties) =| did_inspect_accessibility_tree(u64 page_id, String accessibility_tree) =| did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id) =| did_finish_editing_dom_node(u64 page_id, Optional node_id) =| diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 19f9d3fdf07..bc40bc2f4d5 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -12,6 +12,7 @@ #include #include #include +#include #include endpoint WebContentServer @@ -44,7 +45,8 @@ endpoint WebContentServer debug_request(u64 page_id, ByteString request, ByteString argument) =| get_source(u64 page_id) =| inspect_dom_tree(u64 page_id) =| - inspect_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) =| + inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional pseudo_element) =| + clear_inspected_dom_node(u64 page_id) =| highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) =| inspect_accessibility_tree(u64 page_id) =| get_hovered_node_id(u64 page_id) =| From 92d0cd3c7ccfe7e8ca7e376013af62288dcd01b5 Mon Sep 17 00:00:00 2001 From: Jess Date: Wed, 19 Mar 2025 10:29:06 +1300 Subject: [PATCH 098/141] LibJS: Make `InvalidIndex` detail the full range of allowed values --- Libraries/LibJS/Runtime/ErrorTypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibJS/Runtime/ErrorTypes.h b/Libraries/LibJS/Runtime/ErrorTypes.h index 206e4772ca0..9322c58c76f 100644 --- a/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Libraries/LibJS/Runtime/ErrorTypes.h @@ -77,7 +77,7 @@ M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'") \ M(InvalidFractionDigits, "Fraction Digits must be an integer no less than 0, and no greater than 100") \ M(InvalidHint, "Invalid hint: \"{}\"") \ - M(InvalidIndex, "Index must be a positive integer") \ + M(InvalidIndex, "Index must be a positive integer no greater than 2^53-1") \ M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \ M(InvalidLength, "Invalid {} length") \ M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'") \ From 12cbefbee7b08965788dab375de6b19018059c77 Mon Sep 17 00:00:00 2001 From: Jess Date: Wed, 19 Mar 2025 10:31:39 +1300 Subject: [PATCH 099/141] LibJS+LibCrypto: Use a bitwise approach for BigInt's as*IntN methods This speeds up expressions such as `BigInt.asIntN(0x4000000000000, 1n)` (#3615). And those involving very large bigints. --- .../BigInt/Algorithms/BitwiseOperations.cpp | 10 +++--- .../Algorithms/UnsignedBigIntegerAlgorithms.h | 2 +- .../LibCrypto/BigInt/SignedBigInteger.cpp | 11 ++++++ Libraries/LibCrypto/BigInt/SignedBigInteger.h | 1 + .../LibCrypto/BigInt/UnsignedBigInteger.cpp | 34 +++++++++++++++++- .../LibCrypto/BigInt/UnsignedBigInteger.h | 2 ++ Libraries/LibJS/Runtime/BigIntConstructor.cpp | 36 +++++++++++-------- .../Tests/builtins/BigInt/BigInt.asIntN.js | 25 +++++++++---- .../Tests/builtins/BigInt/BigInt.asUintN.js | 22 ++++++++---- 9 files changed, 110 insertions(+), 33 deletions(-) diff --git a/Libraries/LibCrypto/BigInt/Algorithms/BitwiseOperations.cpp b/Libraries/LibCrypto/BigInt/Algorithms/BitwiseOperations.cpp index 5d240970959..87ffab03e7b 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/BitwiseOperations.cpp +++ b/Libraries/LibCrypto/BigInt/Algorithms/BitwiseOperations.cpp @@ -131,7 +131,7 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_xor_without_allocation( /** * Complexity: O(N) where N is the number of words */ -FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation( +FLATTEN ErrorOr UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation( UnsignedBigInteger const& right, size_t index, UnsignedBigInteger& output) @@ -139,16 +139,16 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w // If the value is invalid, the output value is invalid as well. if (right.is_invalid()) { output.invalidate(); - return; + return {}; } if (index == 0) { output.set_to_0(); - return; + return {}; } size_t size = (index + UnsignedBigInteger::BITS_IN_WORD - 1) / UnsignedBigInteger::BITS_IN_WORD; - output.m_words.resize_and_keep_capacity(size); + TRY(output.m_words.try_resize_and_keep_capacity(size)); VERIFY(size > 0); for (size_t i = 0; i < size - 1; ++i) output.m_words[i] = ~(i < right.length() ? right.words()[i] : 0); @@ -158,6 +158,8 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w auto last_word = last_word_index < right.length() ? right.words()[last_word_index] : 0; output.m_words[last_word_index] = (NumericLimits::max() >> (UnsignedBigInteger::BITS_IN_WORD - index)) & ~last_word; + + return {}; } FLATTEN void UnsignedBigIntegerAlgorithms::shift_left_without_allocation( diff --git a/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h b/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h index bd166c807c7..5536243e6ec 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h +++ b/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h @@ -20,13 +20,13 @@ public: static void bitwise_or_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output); static void bitwise_and_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output); static void bitwise_xor_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output); - static void bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output); static void shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output); static void shift_right_without_allocation(UnsignedBigInteger const& number, size_t num_bits, UnsignedBigInteger& output); static void multiply_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& output); static void divide_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger const& denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder); static void divide_u16_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger::Word denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder); + static ErrorOr bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output); static ErrorOr try_shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output); static void extended_GCD_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& x, UnsignedBigInteger& y, UnsignedBigInteger& gcd, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_r, UnsignedBigInteger& temp_s, UnsignedBigInteger& temp_t); diff --git a/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp b/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp index f1f89069c05..1edba05336a 100644 --- a/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp +++ b/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp @@ -291,6 +291,17 @@ FLATTEN SignedBigInteger SignedBigInteger::shift_right(size_t num_bits) const return SignedBigInteger { m_unsigned_data.shift_right(num_bits), m_sign }; } +FLATTEN ErrorOr SignedBigInteger::mod_power_of_two(size_t power_of_two) const +{ + auto const lower_bits = m_unsigned_data.as_n_bits(power_of_two); + + if (is_positive()) + return SignedBigInteger(lower_bits); + + // twos encode lower bits + return SignedBigInteger(TRY(lower_bits.try_bitwise_not_fill_to_one_based_index(power_of_two)).plus(1).as_n_bits(power_of_two)); +} + FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(SignedBigInteger const& other) const { bool result_sign = m_sign ^ other.m_sign; diff --git a/Libraries/LibCrypto/BigInt/SignedBigInteger.h b/Libraries/LibCrypto/BigInt/SignedBigInteger.h index 73771567774..75203588257 100644 --- a/Libraries/LibCrypto/BigInt/SignedBigInteger.h +++ b/Libraries/LibCrypto/BigInt/SignedBigInteger.h @@ -120,6 +120,7 @@ public: [[nodiscard]] SignedBigInteger multiplied_by(SignedBigInteger const& other) const; [[nodiscard]] SignedDivisionResult divided_by(SignedBigInteger const& divisor) const; + [[nodiscard]] ErrorOr mod_power_of_two(size_t power_of_two) const; [[nodiscard]] ErrorOr try_shift_left(size_t num_bits) const; [[nodiscard]] SignedBigInteger plus(UnsignedBigInteger const& other) const; diff --git a/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp b/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp index 73054427599..b556c5936f5 100644 --- a/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp +++ b/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp @@ -473,10 +473,15 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_xor(UnsignedBigInteger co } FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_not_fill_to_one_based_index(size_t size) const +{ + return MUST(try_bitwise_not_fill_to_one_based_index(size)); +} + +FLATTEN ErrorOr UnsignedBigInteger::try_bitwise_not_fill_to_one_based_index(size_t size) const { UnsignedBigInteger result; - UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result); + TRY(UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result)); return result; } @@ -506,6 +511,33 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_right(size_t num_bits) cons return output; } +FLATTEN UnsignedBigInteger UnsignedBigInteger::as_n_bits(size_t n) const +{ + if (auto const num_bits = one_based_index_of_highest_set_bit(); n >= num_bits) + return *this; + + UnsignedBigInteger output; + output.set_to(*this); + + auto const word_index = n / BITS_IN_WORD; + + auto const bits_to_keep = n % BITS_IN_WORD; + auto const bits_to_discard = BITS_IN_WORD - bits_to_keep; + + output.m_words.resize(word_index + 1); + + auto const last_word = output.m_words[word_index]; + Word new_last_word = 0; + + // avoid UB from a 32 bit shift on a u32 + if (bits_to_keep != 0) + new_last_word = last_word << bits_to_discard >> bits_to_discard; + + output.m_words[word_index] = new_last_word; + + return output; +} + FLATTEN UnsignedBigInteger UnsignedBigInteger::multiplied_by(UnsignedBigInteger const& other) const { UnsignedBigInteger result; diff --git a/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h b/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h index 0e354a51161..7a9997f18b6 100644 --- a/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h +++ b/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h @@ -117,9 +117,11 @@ public: [[nodiscard]] UnsignedBigInteger bitwise_not_fill_to_one_based_index(size_t) const; [[nodiscard]] UnsignedBigInteger shift_left(size_t num_bits) const; [[nodiscard]] UnsignedBigInteger shift_right(size_t num_bits) const; + [[nodiscard]] UnsignedBigInteger as_n_bits(size_t n) const; [[nodiscard]] UnsignedBigInteger multiplied_by(UnsignedBigInteger const& other) const; [[nodiscard]] UnsignedDivisionResult divided_by(UnsignedBigInteger const& divisor) const; + [[nodiscard]] ErrorOr try_bitwise_not_fill_to_one_based_index(size_t) const; [[nodiscard]] ErrorOr try_shift_left(size_t num_bits) const; [[nodiscard]] u32 hash() const; diff --git a/Libraries/LibJS/Runtime/BigIntConstructor.cpp b/Libraries/LibJS/Runtime/BigIntConstructor.cpp index 07f81a009e1..5ebea4b6873 100644 --- a/Libraries/LibJS/Runtime/BigIntConstructor.cpp +++ b/Libraries/LibJS/Runtime/BigIntConstructor.cpp @@ -18,6 +18,7 @@ namespace JS { GC_DEFINE_ALLOCATOR(BigIntConstructor); static Crypto::SignedBigInteger const BIGINT_ONE { 1 }; +static Crypto::SignedBigInteger const BIGINT_ZERO { 0 }; BigIntConstructor::BigIntConstructor(Realm& realm) : NativeFunction(realm.vm().names.BigInt.as_string(), realm.intrinsics().function_prototype()) @@ -72,20 +73,25 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n) // 2. Set bigint to ? ToBigInt(bigint). auto bigint = TRY(vm.argument(1).to_bigint(vm)); + // OPTIMIZATION: mod = bigint (mod 2^0) = 0 < 2^(0-1) = 0.5 + if (bits == 0) + return BigInt::create(vm, BIGINT_ZERO); + // 3. Let mod be ℝ(bigint) modulo 2^bits. - // FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to - // drop the most significant bits. - auto bits_shift_left = TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits)); - auto mod = modulo(bigint->big_integer(), bits_shift_left); + auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits)); - // 4. If mod ≥ 2^(bits-1), return ℤ(mod - 2^bits); otherwise, return ℤ(mod). - // NOTE: Some of the below conditionals are non-standard, but are to protect SignedBigInteger from - // allocating an absurd amount of memory if `bits - 1` overflows to NumericLimits::max. - if ((bits == 0) && (mod >= BIGINT_ONE)) - return BigInt::create(vm, mod.minus(bits_shift_left)); - if ((bits > 0) && (mod >= BIGINT_ONE.shift_left(bits - 1))) - return BigInt::create(vm, mod.minus(bits_shift_left)); + // OPTIMIZATION: mod < 2^(bits-1) + if (mod.is_zero()) + return BigInt::create(vm, BIGINT_ZERO); + // 4. If mod ≥ 2^(bits-1), return ℤ(mod - 2^bits); ... + if (auto top_bit_index = mod.unsigned_value().one_based_index_of_highest_set_bit(); top_bit_index >= bits) { + // twos complement decode + auto decoded = TRY_OR_THROW_OOM(vm, mod.unsigned_value().try_bitwise_not_fill_to_one_based_index(bits)).plus(1); + return BigInt::create(vm, Crypto::SignedBigInteger { std::move(decoded), true }); + } + + // ... otherwise, return ℤ(mod). return BigInt::create(vm, mod); } @@ -98,10 +104,10 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_uint_n) // 2. Set bigint to ? ToBigInt(bigint). auto bigint = TRY(vm.argument(1).to_bigint(vm)); - // 3. Return the BigInt value that represents ℝ(bigint) modulo 2bits. - // FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to - // drop the most significant bits. - return BigInt::create(vm, modulo(bigint->big_integer(), TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits)))); + // 3. Return the BigInt value that represents ℝ(bigint) modulo 2^bits. + auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits)); + + return BigInt::create(vm, mod); } } diff --git a/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asIntN.js b/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asIntN.js index 4bc14a14b0b..eb4424a0b61 100644 --- a/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asIntN.js +++ b/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asIntN.js @@ -22,12 +22,6 @@ describe("errors", () => { BigInt.asIntN(1, "foo"); }).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo"); }); - - test("large allocation", () => { - expect(() => { - BigInt.asIntN(0x4000000000000, 1n); - }).toThrowWithMessage(InternalError, "Out of memory"); - }); }); describe("correct behavior", () => { @@ -82,4 +76,23 @@ describe("correct behavior", () => { expect(BigInt.asIntN(128, -extremelyBigInt)).toBe(99061374399389259395070030194384019691n); expect(BigInt.asIntN(256, -extremelyBigInt)).toBe(-extremelyBigInt); }); + + test("large bit values", () => { + expect(BigInt.asIntN(0x4000000000000, 1n)).toBe(1n); + expect(BigInt.asIntN(0x8ffffffffffff, 1n)).toBe(1n); + expect(BigInt.asIntN(2 ** 53 - 1, 2n)).toBe(2n); + + // These incur large intermediate values that 00M. For now, ensure they don't crash + expect(() => { + BigInt.asIntN(0x4000000000000, -1n); + }).toThrowWithMessage(InternalError, "Out of memory"); + + expect(() => { + BigInt.asIntN(0x8ffffffffffff, -1n); + }).toThrowWithMessage(InternalError, "Out of memory"); + + expect(() => { + BigInt.asIntN(2 ** 53 - 1, -2n); + }).toThrowWithMessage(InternalError, "Out of memory"); + }); }); diff --git a/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asUintN.js b/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asUintN.js index 059d575f557..1d8252f0e96 100644 --- a/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asUintN.js +++ b/Libraries/LibJS/Tests/builtins/BigInt/BigInt.asUintN.js @@ -22,12 +22,6 @@ describe("errors", () => { BigInt.asUintN(1, "foo"); }).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo"); }); - - test("large allocation", () => { - expect(() => { - BigInt.asUintN(0x4000000000000, 1n); - }).toThrowWithMessage(InternalError, "Out of memory"); - }); }); describe("correct behavior", () => { @@ -80,4 +74,20 @@ describe("correct behavior", () => { 115792089237316195423570861551898784396480861208851440582668460551124006183147n ); }); + + test("large bit values", () => { + const INDEX_MAX = 2 ** 53 - 1; + const LAST_8_DIGITS = 10n ** 8n; + + expect(BigInt.asUintN(0x400000000, 1n)).toBe(1n); + expect(BigInt.asUintN(0x4000, -1n) % LAST_8_DIGITS).toBe(64066815n); + + expect(BigInt.asUintN(0x400000000, 2n)).toBe(2n); + expect(BigInt.asUintN(0x4000, -2n) % LAST_8_DIGITS).toBe(64066814n); + + expect(BigInt.asUintN(INDEX_MAX, 2n)).toBe(2n); + expect(() => { + BigInt.asUintN(INDEX_MAX, -2n); + }).toThrowWithMessage(InternalError, "Out of memory"); + }); }); From ea8213f7fae09b028623a57ef09887c1492e47a9 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 20 Mar 2025 08:58:20 +0100 Subject: [PATCH 100/141] Meta: Add Open Collective to FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index e9663ae2f1a..f78447b6474 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ custom: https://donorbox.org/ladybird +open_collective: ladybird polar: LadybirdBrowser From 86a93b9b4763ccbf0684919283241ab5901363f0 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 17:02:18 -0400 Subject: [PATCH 101/141] UI/Qt: Prevent UAF while parsing autocomplete response data JsonParser only holds a view into the provided string, the caller must keep it alive. Though we can actually just use JsonValue::from_string here instead. --- UI/Qt/AutoComplete.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/UI/Qt/AutoComplete.cpp b/UI/Qt/AutoComplete.cpp index abfd7c19975..90306dc7126 100644 --- a/UI/Qt/AutoComplete.cpp +++ b/UI/Qt/AutoComplete.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -111,8 +110,8 @@ ErrorOr AutoComplete::got_network_response(QNetworkReply* reply) if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError) return {}; - AK::JsonParser parser(ak_byte_string_from_qstring(reply->readAll())); - auto json = TRY(parser.parse()); + auto reply_data = ak_string_from_qstring(reply->readAll()); + auto json = TRY(JsonValue::from_string(reply_data)); auto engine_name = Settings::the()->autocomplete_engine().name; Vector results; From 8847079d8a454ccff3c816ee0c482b0103e2f75e Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 17:05:02 -0400 Subject: [PATCH 102/141] UI/Qt: Invert check for DuckDuckGo autocomplete parse results --- UI/Qt/AutoComplete.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/Qt/AutoComplete.cpp b/UI/Qt/AutoComplete.cpp index 90306dc7126..aa3ff3e5d83 100644 --- a/UI/Qt/AutoComplete.cpp +++ b/UI/Qt/AutoComplete.cpp @@ -69,7 +69,7 @@ ErrorOr> AutoComplete::parse_duckduckgo_autocomplete(Vector Date: Wed, 19 Mar 2025 17:15:53 -0400 Subject: [PATCH 103/141] UI/Qt: Do not make assumptions about autocomplete response types For example, we expect a JSON array from Google. Let's not crash if we get a JSON object or some other unexpected type. --- UI/Qt/AutoComplete.cpp | 82 ++++++++++++++++++++++++------------------ UI/Qt/AutoComplete.h | 6 ++-- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/UI/Qt/AutoComplete.cpp b/UI/Qt/AutoComplete.cpp index aa3ff3e5d83..02e3f724bc6 100644 --- a/UI/Qt/AutoComplete.cpp +++ b/UI/Qt/AutoComplete.cpp @@ -37,67 +37,79 @@ AutoComplete::AutoComplete(QWidget* parent) }); } -ErrorOr> AutoComplete::parse_google_autocomplete(Vector const& json) +ErrorOr> AutoComplete::parse_google_autocomplete(JsonValue const& json) { - if (json.size() != 5) - return Error::from_string_literal("Invalid JSON, expected 5 elements in array"); + if (!json.is_array()) + return Error::from_string_literal("Expected Google autocomplete response to be a JSON array"); - if (!json[0].is_string()) - return Error::from_string_literal("Invalid JSON, expected first element to be a string"); - auto query = json[0].as_string(); + auto const& values = json.as_array(); - if (!json[1].is_array()) - return Error::from_string_literal("Invalid JSON, expected second element to be an array"); - auto suggestions_array = json[1].as_array().values(); + if (values.size() != 5) + return Error::from_string_literal("Invalid Google autocomplete response, expected 5 elements in array"); + if (!values[0].is_string()) + return Error::from_string_literal("Invalid Google autocomplete response, expected first element to be a string"); + + auto const& query = values[0].as_string(); if (query != m_query) - return Error::from_string_literal("Invalid JSON, query does not match"); + return Error::from_string_literal("Invalid Google autocomplete response, query does not match"); + + if (!values[1].is_array()) + return Error::from_string_literal("Invalid Google autocomplete response, expected second element to be an array"); + auto const& suggestions_array = values[1].as_array().values(); Vector results; results.ensure_capacity(suggestions_array.size()); - for (auto& suggestion : suggestions_array) + for (auto const& suggestion : suggestions_array) results.unchecked_append(suggestion.as_string()); return results; } -ErrorOr> AutoComplete::parse_duckduckgo_autocomplete(Vector const& json) +ErrorOr> AutoComplete::parse_duckduckgo_autocomplete(JsonValue const& json) { - Vector results; + if (!json.is_array()) + return Error::from_string_literal("Expected DuckDuckGo autocomplete response to be a JSON array"); - for (auto const& suggestion : json) { + Vector results; + results.ensure_capacity(json.as_array().size()); + + for (auto const& suggestion : json.as_array().values()) { if (!suggestion.is_object()) - return Error::from_string_literal("Invalid JSON, expected value to be an object"); + return Error::from_string_literal("Invalid DuckDuckGo autocomplete response, expected value to be an object"); if (auto value = suggestion.as_object().get_string("phrase"sv); value.has_value()) - results.append(*value); + results.unchecked_append(*value); } return results; } -ErrorOr> AutoComplete::parse_yahoo_autocomplete(JsonObject const& json) +ErrorOr> AutoComplete::parse_yahoo_autocomplete(JsonValue const& json) { - auto query = json.get_string("q"sv); - if (!query.has_value()) - return Error::from_string_view("Invalid JSON, expected \"q\" to be a string"sv); - if (query != m_query) - return Error::from_string_literal("Invalid JSON, query does not match"); + if (!json.is_object()) + return Error::from_string_literal("Expected Yahoo autocomplete response to be a JSON array"); - auto suggestions = json.get_array("r"sv); + auto query = json.as_object().get_string("q"sv); + if (!query.has_value()) + return Error::from_string_literal("Invalid Yahoo autocomplete response, expected \"q\" to be a string"); + if (query != m_query) + return Error::from_string_literal("Invalid Yahoo autocomplete response, query does not match"); + + auto suggestions = json.as_object().get_array("r"sv); if (!suggestions.has_value()) - return Error::from_string_view("Invalid JSON, expected \"r\" to be an object"sv); + return Error::from_string_literal("Invalid Yahoo autocomplete response, expected \"r\" to be an object"); Vector results; results.ensure_capacity(suggestions->size()); for (auto const& suggestion : suggestions->values()) { if (!suggestion.is_object()) - return Error::from_string_literal("Invalid JSON, expected value to be an object"); + return Error::from_string_literal("Invalid Yahoo autocomplete response, expected value to be an object"); auto result = suggestion.as_object().get_string("k"sv); if (!result.has_value()) - return Error::from_string_view("Invalid JSON, expected \"k\" to be a string"sv); + return Error::from_string_literal("Invalid Yahoo autocomplete response, expected \"k\" to be a string"); results.unchecked_append(*result); } @@ -113,17 +125,17 @@ ErrorOr AutoComplete::got_network_response(QNetworkReply* reply) auto reply_data = ak_string_from_qstring(reply->readAll()); auto json = TRY(JsonValue::from_string(reply_data)); - auto engine_name = Settings::the()->autocomplete_engine().name; + auto const& engine_name = Settings::the()->autocomplete_engine().name; + Vector results; - if (engine_name == "Google") { - results = TRY(parse_google_autocomplete(json.as_array().values())); - } else if (engine_name == "DuckDuckGo") { - results = TRY(parse_duckduckgo_autocomplete(json.as_array().values())); - } else if (engine_name == "Yahoo") - results = TRY(parse_yahoo_autocomplete(json.as_object())); - else { + if (engine_name == "Google") + results = TRY(parse_google_autocomplete(json)); + else if (engine_name == "DuckDuckGo") + results = TRY(parse_duckduckgo_autocomplete(json)); + else if (engine_name == "Yahoo") + results = TRY(parse_yahoo_autocomplete(json)); + else return Error::from_string_literal("Invalid engine name"); - } constexpr size_t MAX_AUTOCOMPLETE_RESULTS = 6; if (results.is_empty()) { diff --git a/UI/Qt/AutoComplete.h b/UI/Qt/AutoComplete.h index 580cba59342..c655bee5afa 100644 --- a/UI/Qt/AutoComplete.h +++ b/UI/Qt/AutoComplete.h @@ -79,9 +79,9 @@ private: ErrorOr got_network_response(QNetworkReply* reply); - ErrorOr> parse_google_autocomplete(Vector const&); - ErrorOr> parse_duckduckgo_autocomplete(Vector const&); - ErrorOr> parse_yahoo_autocomplete(JsonObject const&); + ErrorOr> parse_google_autocomplete(JsonValue const&); + ErrorOr> parse_duckduckgo_autocomplete(JsonValue const&); + ErrorOr> parse_yahoo_autocomplete(JsonValue const&); QNetworkAccessManager* m_manager; AutoCompleteModel* m_auto_complete_model; From 64aaf73775a20d1e0881eb06cb2365cefea1479f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 17:46:08 -0400 Subject: [PATCH 104/141] LibWeb: Avoid using JsonParser directly in WebDriver No need to construct a temporary parser. --- Libraries/LibWeb/WebDriver/Client.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Libraries/LibWeb/WebDriver/Client.cpp b/Libraries/LibWeb/WebDriver/Client.cpp index cb44ffe1e6a..acd7bf243d5 100644 --- a/Libraries/LibWeb/WebDriver/Client.cpp +++ b/Libraries/LibWeb/WebDriver/Client.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -258,8 +257,7 @@ ErrorOr Client::read_body_as_json(HTTP::HttpReq if (content_length == 0) return JsonValue {}; - JsonParser json_parser(request.body()); - return TRY(json_parser.parse()); + return TRY(JsonValue::from_string(request.body())); } ErrorOr Client::handle_request(HTTP::HttpRequest const& request, JsonValue body) From 086a921213066607017e31b0ebbad49da02b2285 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 17:47:25 -0400 Subject: [PATCH 105/141] AK: Disallow construction of JsonParser JsonParser has a footgun where it does not retain ownership of the string to be parsed. For example, the following results in UAF: JsonParser parser(something_returning_a_string()); parser.parse(); Let's avoid this altogether by only allowing use of JsonParser with a static, safe method. --- AK/JsonParser.cpp | 8 +++++++- AK/JsonParser.h | 7 ++++--- AK/JsonValue.cpp | 2 +- Meta/Lagom/Fuzzers/FuzzJsonParser.cpp | 3 +-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/AK/JsonParser.cpp b/AK/JsonParser.cpp index fb29a4fab4d..611ad0d5c21 100644 --- a/AK/JsonParser.cpp +++ b/AK/JsonParser.cpp @@ -18,6 +18,12 @@ constexpr bool is_space(int ch) return ch == '\t' || ch == '\n' || ch == '\r' || ch == ' '; } +ErrorOr JsonParser::parse(StringView input) +{ + JsonParser parser(input); + return parser.parse_json(); +} + // ECMA-404 9 String // Boils down to // STRING = "\"" *("[^\"\\]" | "\\" ("[\"\\bfnrt]" | "u[0-9A-Za-z]{4}")) "\"" @@ -335,7 +341,7 @@ ErrorOr JsonParser::parse_helper() return Error::from_string_literal("JsonParser: Unexpected character"); } -ErrorOr JsonParser::parse() +ErrorOr JsonParser::parse_json() { auto result = TRY(parse_helper()); ignore_while(is_space); diff --git a/AK/JsonParser.h b/AK/JsonParser.h index ccea7447c50..6fae427f531 100644 --- a/AK/JsonParser.h +++ b/AK/JsonParser.h @@ -13,14 +13,15 @@ namespace AK { class JsonParser : private GenericLexer { public: + static ErrorOr parse(StringView); + +private: explicit JsonParser(StringView input) : GenericLexer(input) { } - ErrorOr parse(); - -private: + ErrorOr parse_json(); ErrorOr parse_helper(); ErrorOr consume_and_unescape_string(); diff --git a/AK/JsonValue.cpp b/AK/JsonValue.cpp index a816172f0e5..730872cd6cd 100644 --- a/AK/JsonValue.cpp +++ b/AK/JsonValue.cpp @@ -190,7 +190,7 @@ JsonValue::JsonValue(JsonArray&& value) ErrorOr JsonValue::from_string(StringView input) { - return JsonParser(input).parse(); + return JsonParser::parse(input); } String JsonValue::serialized() const diff --git a/Meta/Lagom/Fuzzers/FuzzJsonParser.cpp b/Meta/Lagom/Fuzzers/FuzzJsonParser.cpp index 87566e7928e..86feb73994e 100644 --- a/Meta/Lagom/Fuzzers/FuzzJsonParser.cpp +++ b/Meta/Lagom/Fuzzers/FuzzJsonParser.cpp @@ -9,7 +9,6 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) { AK::set_debug_enabled(false); - JsonParser parser({ data, size }); - (void)parser.parse(); + (void)JsonParser::parse({ data, size }); return 0; } From 6f69a445bd3d2fd5bbd61b3b3cea47df18fa6242 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 20 Mar 2025 09:52:38 +0100 Subject: [PATCH 106/141] CI: Only run builds after successful linting We can prevent running builds unnecessarily by requiring the linters to succeed first. If either the code or commit linter fails, it means the author of the PR needs to rework their branch and after pushing their changes, we need to do a full new CI run anyway. --- .github/workflows/ci.yml | 9 +++++++++ .github/workflows/lint-code.yml | 3 ++- .github/workflows/lint-commits.yml | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d778d26d05f..c1e91365895 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,9 +7,18 @@ concurrency: cancel-in-progress: true jobs: + lint_code: + name: 'Lint Code' + uses: ./.github/workflows/lint-code.yml + + lint_commits: + name: 'Lint Commits' + uses: ./.github/workflows/lint-commits.yml + # CI matrix - runs the job in lagom-template.yml with different configurations. Lagom: if: github.repository == 'LadybirdBrowser/ladybird' + needs: [lint_code, lint_commits] strategy: fail-fast: false diff --git a/.github/workflows/lint-code.yml b/.github/workflows/lint-code.yml index ca901dba922..964b320e87c 100644 --- a/.github/workflows/lint-code.yml +++ b/.github/workflows/lint-code.yml @@ -1,6 +1,7 @@ name: Lint Code -on: [ push, pull_request ] +# Used by ci.yml +on: [workflow_call] jobs: lint: diff --git a/.github/workflows/lint-commits.yml b/.github/workflows/lint-commits.yml index e27b511ccae..12e359ac298 100644 --- a/.github/workflows/lint-commits.yml +++ b/.github/workflows/lint-commits.yml @@ -1,6 +1,7 @@ name: Lint Commit Messages -on: [pull_request_target] +# Used by ci.yml +on: [workflow_call] # Make sure to update Meta/lint-commit.sh to match this script when adding new checks! # (… but don't accept overlong 'fixup!' commit descriptions.) From bf333eaea253ebf47c7d6192ff0c42e7a198d606 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 20 Mar 2025 11:29:33 +0100 Subject: [PATCH 107/141] CI: Only run commit linter for pull requests, not pushes This requires us to always run the CI job and check the individual jobs' results, since only having `needs:` will not work when `lint_commits` is potentially skipped. --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1e91365895..9a150a26633 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,17 @@ jobs: lint_commits: name: 'Lint Commits' + if: ${{ github.event_name == 'pull_request' }} uses: ./.github/workflows/lint-commits.yml # CI matrix - runs the job in lagom-template.yml with different configurations. Lagom: - if: github.repository == 'LadybirdBrowser/ladybird' needs: [lint_code, lint_commits] + if: | + always() + && github.repository == 'LadybirdBrowser/ladybird' + && needs.lint_code.result == 'success' + && (needs.lint_commits.result == 'skipped' || needs.lint_commits.result == 'success') strategy: fail-fast: false From a5a84159a35d8a43a73d74ff3901e31f78172238 Mon Sep 17 00:00:00 2001 From: Psychpsyo Date: Tue, 18 Mar 2025 19:20:35 +0100 Subject: [PATCH 108/141] Meta: Add DOCTYPEs to sceenshot tests input-placeholder-ref was modified because the input box ends up being slightly wider outside of quirks mode. --- .../Screenshot/expected/alt-frame-ref.html | 1 + .../expected/border-radius-ref.html | 1 + .../canvas-arcs-and-ellipses-ref.html | 1 + .../canvas-fillstyle-gradients-ref.html | 1 + .../canvas-fillstyle-opacity-ref.html | 1 + .../expected/canvas-fillstyle-rgb-ref.html | 1 + .../expected/canvas-filters-ref.html | 1 + .../canvas-implict-moves-and-lines-ref.html | 1 + .../expected/canvas-path-rect-ref.html | 1 + .../expected/canvas-shadow-ref.html | 1 + .../expected/canvas-stroke-styles-ref.html | 1 + .../Screenshot/expected/canvas-text-ref.html | 1 + .../canvas-unpremultiplied-image-ref.html | 1 + .../expected/clip-path-basic-shapes-ref.html | 1 + .../expected/clip-path-polygon-ref.html | 1 + .../expected/clip-path-transformed-ref.html | 1 + .../Screenshot/expected/color-scheme-ref.html | 1 + .../css-background-clip-text-ref.html | 1 + .../expected/css-background-position-ref.html | 1 + .../expected/css-background-repeat-ref.html | 1 + .../expected/css-backgrounds-ref.html | 1 + .../expected/css-color-functions-ref.html | 1 + .../expected/css-filter-drop-shadow-ref.html | 1 + .../Screenshot/expected/css-filter-ref.html | 1 + .../expected/css-gradients-ref.html | 1 + .../expected/css-transform-box-ref.html | 1 + .../gradient-interpolation-method-ref.html | 1 + .../Screenshot/expected/inline-node-ref.html | 1 + .../expected/input-placeholder-ref.html | 1 + .../LibWeb/Screenshot/expected/meter-ref.html | 1 + ...hidden-overflow-and-border-radius-ref.html | 1 + .../expected/object-fit-position-ref.html | 1 + .../expected/opacity-stacking-ref.html | 1 + .../Screenshot/expected/ordered-list-ref.html | 1 + .../expected/outer-box-shadow-ref.html | 1 + .../expected/svg-axis-aligned-lines-ref.html | 1 + .../svg-background-no-natural-size-ref.html | 1 + .../expected/svg-clip-path-and-mask-ref.html | 1 + .../expected/svg-clip-path-transform-ref.html | 1 + .../expected/svg-clip-rule-ref.html | 1 + .../expected/svg-foreign-object-mask-ref.html | 1 + ...svg-gradient-paint-transformation-ref.html | 1 + .../svg-gradient-spreadMethod-ref.html | 1 + .../svg-gradient-userSpaceOnUse-ref.html | 1 + .../expected/svg-maskContentUnits-ref.html | 1 + .../expected/svg-non-local-clip-path-ref.html | 1 + .../svg-path-offset-rounding-ref.html | 1 + .../expected/svg-radialGradient-ref.html | 1 + .../expected/svg-simple-clipPath-ref.html | 1 + ...vg-stroke-paintstyle-with-opacity-ref.html | 1 + .../expected/svg-stroke-styles-ref.html | 1 + .../expected/svg-text-effects-ref.html | 1 + .../Screenshot/expected/svg-textPath-ref.html | 1 + .../expected/text-decorations-ref.html | 1 + .../expected/text-direction-ref.html | 1 + .../Screenshot/expected/text-shadow-ref.html | 1 + .../images/input-placeholder-ref.png | Bin 1659 -> 1657 bytes .../input/canvas-arcs-and-ellipses.html | 1 + .../input/canvas-fillstyle-gradients.html | 1 + .../input/canvas-fillstyle-opacity.html | 1 + .../input/canvas-fillstyle-rgb.html | 1 + .../Screenshot/input/canvas-filters.html | 1 + .../input/canvas-implict-moves-and-lines.html | 1 + .../Screenshot/input/canvas-path-rect.html | 1 + .../Screenshot/input/canvas-shadow.html | 1 + .../input/canvas-stroke-styles.html | 1 + .../LibWeb/Screenshot/input/canvas-text.html | 1 + .../input/canvas-unpremultiplied-image.html | 1 + .../input/clip-path-basic-shapes.html | 1 + .../Screenshot/input/clip-path-polygon.html | 1 + .../input/clip-path-transformed.html | 1 + .../LibWeb/Screenshot/input/color-scheme.html | 1 + .../LibWeb/Screenshot/input/inline-node.html | 1 - .../Screenshot/input/input-placeholder.html | 1 + Tests/LibWeb/Screenshot/input/meter.html | 1 + .../Screenshot/input/object-fit-position.html | 1 + .../Screenshot/input/opacity-stacking.html | 1 + .../LibWeb/Screenshot/input/ordered-list.html | 1 + .../Screenshot/input/outer-box-shadow.html | 1 + .../input/svg-clip-path-and-mask.html | 1 + .../input/svg-clip-path-transform.html | 1 + .../Screenshot/input/svg-clip-rule.html | 1 + .../svg-gradient-paint-transformation.html | 1 + .../input/svg-gradient-spreadMethod.html | 1 + .../input/svg-gradient-userSpaceOnUse.html | 1 + .../input/svg-maskContentUnits.html | 1 + .../Screenshot/input/svg-radialGradient.html | 1 + .../Screenshot/input/svg-simple-clipPath.html | 1 + .../svg-stroke-paintstyle-with-opacity.html | 1 + .../Screenshot/input/svg-stroke-styles.html | 1 + .../Screenshot/input/svg-text-effects.html | 1 + .../LibWeb/Screenshot/input/svg-textPath.html | 1 + 92 files changed, 90 insertions(+), 1 deletion(-) diff --git a/Tests/LibWeb/Screenshot/expected/alt-frame-ref.html b/Tests/LibWeb/Screenshot/expected/alt-frame-ref.html index 9bd571b2ff4..8d1abcf9c14 100644 --- a/Tests/LibWeb/Screenshot/expected/alt-frame-ref.html +++ b/Tests/LibWeb/Screenshot/expected/alt-frame-ref.html @@ -1,3 +1,4 @@ +
diff --git a/Tests/LibWeb/Text/input/UIEvents/MouseEvent-bubbling.html b/Tests/LibWeb/Text/input/UIEvents/MouseEvent-bubbling.html index c2e56fe29f3..96cfb37b03a 100644 --- a/Tests/LibWeb/Text/input/UIEvents/MouseEvent-bubbling.html +++ b/Tests/LibWeb/Text/input/UIEvents/MouseEvent-bubbling.html @@ -1,3 +1,4 @@ + diff --git a/Tests/LibWeb/Text/input/css/keyframes-css-rules.html b/Tests/LibWeb/Text/input/css/keyframes-css-rules.html index d920b1cd6da..82cb500d235 100644 --- a/Tests/LibWeb/Text/input/css/keyframes-css-rules.html +++ b/Tests/LibWeb/Text/input/css/keyframes-css-rules.html @@ -1,3 +1,4 @@ + diff --git a/Tests/LibWeb/Text/input/css/sending-animationcancel-event-crash.html b/Tests/LibWeb/Text/input/css/sending-animationcancel-event-crash.html index dfa5b6856c7..cf378e6f013 100644 --- a/Tests/LibWeb/Text/input/css/sending-animationcancel-event-crash.html +++ b/Tests/LibWeb/Text/input/css/sending-animationcancel-event-crash.html @@ -1,3 +1,4 @@ + diff --git a/Tests/LibWeb/Ref/input/options-set-index.html b/Tests/LibWeb/Ref/input/options-set-index.html index 4cd3b17bc84..e5f22ed82cc 100644 --- a/Tests/LibWeb/Ref/input/options-set-index.html +++ b/Tests/LibWeb/Ref/input/options-set-index.html @@ -1,3 +1,4 @@ + +
From c40f88ddcbc9f83847157aecf5f6cca0a71c4f14 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 20 Mar 2025 18:50:45 +0100 Subject: [PATCH 135/141] LibWeb: Move minimum/maximum size getter into GridItem method in GFC No behavior change intended. --- .../LibWeb/Layout/GridFormattingContext.cpp | 22 ++++--------------- .../LibWeb/Layout/GridFormattingContext.h | 15 +++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 27c2d8beb40..3b58ae2382c 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -2410,20 +2410,6 @@ AvailableSpace GridFormattingContext::get_available_space_for_item(GridItem cons return AvailableSpace(available_width, available_height); } -static CSS::Size const& get_item_minimum_size(GridItem const& item, GridDimension const dimension) -{ - if (dimension == GridDimension::Column) - return item.box->computed_values().min_width(); - return item.box->computed_values().min_height(); -} - -static CSS::Size const& get_item_maximum_size(GridItem const& item, GridDimension const dimension) -{ - if (dimension == GridDimension::Column) - return item.box->computed_values().max_width(); - return item.box->computed_values().max_height(); -} - CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem const& item, GridDimension const dimension) const { auto available_space_for_item = get_available_space_for_item(item); @@ -2435,7 +2421,7 @@ CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem con }(); auto maxium_size = CSSPixels::max(); - if (auto const& css_maximum_size = get_item_maximum_size(item, dimension); css_maximum_size.is_length()) { + if (auto const& css_maximum_size = item.maximum_size(dimension); css_maximum_size.is_length()) { maxium_size = css_maximum_size.length().to_px(item.box); } @@ -2464,7 +2450,7 @@ CSSPixels GridFormattingContext::calculate_max_content_contribution(GridItem con }(); auto maxium_size = CSSPixels::max(); - if (auto const& css_maximum_size = get_item_maximum_size(item, dimension); css_maximum_size.is_length()) { + if (auto const& css_maximum_size = item.maximum_size(dimension); css_maximum_size.is_length()) { maxium_size = css_maximum_size.length().to_px(item.box); } @@ -2618,7 +2604,7 @@ CSSPixels GridFormattingContext::content_based_minimum_size(GridItem const& item } // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite. - if (auto const& css_maximum_size = get_item_maximum_size(item, dimension); css_maximum_size.is_length()) { + if (auto const& css_maximum_size = item.maximum_size(dimension); css_maximum_size.is_length()) { auto maximum_size = css_maximum_size.length().to_px(item.box); result = min(result, maximum_size); } @@ -2673,7 +2659,7 @@ CSSPixels GridFormattingContext::calculate_minimum_contribution(GridItem const& }(); if (should_treat_preferred_size_as_auto) { - auto minimum_size = get_item_minimum_size(item, dimension); + auto minimum_size = item.minimum_size(dimension); if (minimum_size.is_auto()) return item.add_margin_box_sizes(automatic_minimum_size(item, dimension), dimension); auto containing_block_size = containing_block_size_for_item(item, dimension); diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.h b/Libraries/LibWeb/Layout/GridFormattingContext.h index f13078f8f0f..7a2cb9220a2 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -68,6 +68,21 @@ struct GridItem { [[nodiscard]] int gap_adjusted_row() const; [[nodiscard]] int gap_adjusted_column() const; + + CSS::ComputedValues const& computed_values() const + { + return box->computed_values(); + } + + CSS::Size const& minimum_size(GridDimension dimension) const + { + return dimension == GridDimension::Column ? computed_values().min_width() : computed_values().min_height(); + } + + CSS::Size const& maximum_size(GridDimension dimension) const + { + return dimension == GridDimension::Column ? computed_values().max_width() : computed_values().max_height(); + } }; enum class FoundUnoccupiedPlace { From 2ce40b5687ab6f78ade4304739a30e5fa16551d6 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 20 Mar 2025 20:43:55 +0100 Subject: [PATCH 136/141] LibWeb: Move preferred size getter into GridItem method in GFC No behavior change intended. --- .../LibWeb/Layout/GridFormattingContext.cpp | 17 +++++------------ Libraries/LibWeb/Layout/GridFormattingContext.h | 7 +++++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 3b58ae2382c..223f37ce283 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -2369,13 +2369,6 @@ CSSPixels GridFormattingContext::calculate_grid_container_maximum_size(GridDimen return calculate_inner_height(grid_container(), m_available_space.value(), computed_values.max_height()); } -CSS::Size const& GridFormattingContext::get_item_preferred_size(GridItem const& item, GridDimension const dimension) const -{ - if (dimension == GridDimension::Column) - return item.box->computed_values().width(); - return item.box->computed_values().height(); -} - CSSPixels GridFormattingContext::calculate_min_content_size(GridItem const& item, GridDimension const dimension) const { if (dimension == GridDimension::Column) { @@ -2430,7 +2423,7 @@ CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem con return min(result, maxium_size); } - auto preferred_size = get_item_preferred_size(item, dimension); + auto preferred_size = item.preferred_size(dimension); if (dimension == GridDimension::Column) { auto width = calculate_inner_width(item.box, m_available_space->width, preferred_size); return min(item.add_margin_box_sizes(width, dimension), maxium_size); @@ -2454,7 +2447,7 @@ CSSPixels GridFormattingContext::calculate_max_content_contribution(GridItem con maxium_size = css_maximum_size.length().to_px(item.box); } - auto preferred_size = get_item_preferred_size(item, dimension); + auto preferred_size = item.preferred_size(dimension); if (should_treat_preferred_size_as_auto || preferred_size.is_fit_content()) { auto fit_content_size = dimension == GridDimension::Column ? calculate_fit_content_width(item.box, available_space_for_item) : calculate_fit_content_height(item.box, available_space_for_item); auto result = item.add_margin_box_sizes(fit_content_size, dimension); @@ -2535,7 +2528,7 @@ Optional GridFormattingContext::specified_size_suggestion(GridItem co if (has_definite_preferred_size) { // FIXME: consider margins, padding and borders because it is outer size. auto containing_block_size = containing_block_size_for_item(item, dimension); - return get_item_preferred_size(item, dimension).to_px(item.box, containing_block_size); + return item.preferred_size(dimension).to_px(item.box, containing_block_size); } return {}; @@ -2551,7 +2544,7 @@ Optional GridFormattingContext::transferred_size_suggestion(GridItem return {}; } - CSS::Size const& preferred_size_in_opposite_axis = get_item_preferred_size(item, dimension == GridDimension::Column ? GridDimension::Row : GridDimension::Column); + CSS::Size const& preferred_size_in_opposite_axis = item.preferred_size(dimension == GridDimension::Column ? GridDimension::Row : GridDimension::Column); if (preferred_size_in_opposite_axis.is_length()) { auto opposite_axis_size = preferred_size_in_opposite_axis.length().to_px(item.box); // FIXME: Clamp by opposite-axis minimum and maximum sizes if they are definite @@ -2651,7 +2644,7 @@ CSSPixels GridFormattingContext::calculate_minimum_contribution(GridItem const& // contribution is its min-content contribution. Because the minimum contribution often depends on // the size of the item’s content, it is considered a type of intrinsic size contribution. - auto preferred_size = get_item_preferred_size(item, dimension); + auto preferred_size = item.preferred_size(dimension); auto should_treat_preferred_size_as_auto = [&] { if (dimension == GridDimension::Column) return should_treat_width_as_auto(item.box, get_available_space_for_item(item)); diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.h b/Libraries/LibWeb/Layout/GridFormattingContext.h index 7a2cb9220a2..dd2477d1904 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -83,6 +83,11 @@ struct GridItem { { return dimension == GridDimension::Column ? computed_values().max_width() : computed_values().max_height(); } + + CSS::Size const& preferred_size(GridDimension dimension) const + { + return dimension == GridDimension::Column ? computed_values().width() : computed_values().height(); + } }; enum class FoundUnoccupiedPlace { @@ -333,8 +338,6 @@ private: CSSPixels calculate_grid_container_maximum_size(GridDimension const) const; - CSS::Size const& get_item_preferred_size(GridItem const&, GridDimension const) const; - CSSPixels calculate_min_content_size(GridItem const&, GridDimension const) const; CSSPixels calculate_max_content_size(GridItem const&, GridDimension const) const; From 484cd8a0c35445e2d126e4aba130f4aed3d65546 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 20 Mar 2025 20:49:10 +0100 Subject: [PATCH 137/141] LibWeb: Move item's available space getter into GridItem method in GFC No behavior change intended. --- .../LibWeb/Layout/GridFormattingContext.cpp | 25 ++++++------------- .../LibWeb/Layout/GridFormattingContext.h | 8 +++++- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 223f37ce283..ce7d1e61ea8 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -1719,9 +1719,9 @@ void GridFormattingContext::resolve_grid_item_heights() ItemAlignment used_alignment; if (computed_height.is_auto()) { - used_alignment = try_compute_height(calculate_fit_content_height(item.box, get_available_space_for_item(item))); + used_alignment = try_compute_height(calculate_fit_content_height(item.box, item.available_space())); } else if (computed_height.is_fit_content()) { - used_alignment = try_compute_height(calculate_fit_content_height(item.box, get_available_space_for_item(item))); + used_alignment = try_compute_height(calculate_fit_content_height(item.box, item.available_space())); } else { used_alignment = try_compute_height(computed_height.to_px(grid_container(), containing_block_height)); } @@ -2373,18 +2373,16 @@ CSSPixels GridFormattingContext::calculate_min_content_size(GridItem const& item { if (dimension == GridDimension::Column) { return calculate_min_content_width(item.box); - } else { - return calculate_min_content_height(item.box, get_available_space_for_item(item).width.to_px_or_zero()); } + return calculate_min_content_height(item.box, item.available_space().width.to_px_or_zero()); } CSSPixels GridFormattingContext::calculate_max_content_size(GridItem const& item, GridDimension const dimension) const { if (dimension == GridDimension::Column) { return calculate_max_content_width(item.box); - } else { - return calculate_max_content_height(item.box, get_available_space_for_item(item).width.to_px_or_zero()); } + return calculate_max_content_height(item.box, item.available_space().width.to_px_or_zero()); } CSSPixels GridFormattingContext::containing_block_size_for_item(GridItem const& item, GridDimension const dimension) const @@ -2396,16 +2394,9 @@ CSSPixels GridFormattingContext::containing_block_size_for_item(GridItem const& return containing_block_size; } -AvailableSpace GridFormattingContext::get_available_space_for_item(GridItem const& item) const -{ - AvailableSize available_width = item.used_values.has_definite_width() ? AvailableSize::make_definite(item.used_values.content_width()) : AvailableSize::make_indefinite(); - AvailableSize available_height = item.used_values.has_definite_height() ? AvailableSize::make_definite(item.used_values.content_height()) : AvailableSize::make_indefinite(); - return AvailableSpace(available_width, available_height); -} - CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem const& item, GridDimension const dimension) const { - auto available_space_for_item = get_available_space_for_item(item); + auto available_space_for_item = item.available_space(); auto should_treat_preferred_size_as_auto = [&] { if (dimension == GridDimension::Column) @@ -2434,7 +2425,7 @@ CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem con CSSPixels GridFormattingContext::calculate_max_content_contribution(GridItem const& item, GridDimension const dimension) const { - auto available_space_for_item = get_available_space_for_item(item); + auto available_space_for_item = item.available_space(); auto should_treat_preferred_size_as_auto = [&] { if (dimension == GridDimension::Column) @@ -2647,8 +2638,8 @@ CSSPixels GridFormattingContext::calculate_minimum_contribution(GridItem const& auto preferred_size = item.preferred_size(dimension); auto should_treat_preferred_size_as_auto = [&] { if (dimension == GridDimension::Column) - return should_treat_width_as_auto(item.box, get_available_space_for_item(item)); - return should_treat_height_as_auto(item.box, get_available_space_for_item(item)); + return should_treat_width_as_auto(item.box, item.available_space()); + return should_treat_height_as_auto(item.box, item.available_space()); }(); if (should_treat_preferred_size_as_auto) { diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.h b/Libraries/LibWeb/Layout/GridFormattingContext.h index dd2477d1904..755fa85b6b0 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -88,6 +88,13 @@ struct GridItem { { return dimension == GridDimension::Column ? computed_values().width() : computed_values().height(); } + + AvailableSpace available_space() const + { + auto available_width = used_values.has_definite_width() ? AvailableSize::make_definite(used_values.content_width()) : AvailableSize::make_indefinite(); + auto available_height = used_values.has_definite_height() ? AvailableSize::make_definite(used_values.content_height()) : AvailableSize::make_indefinite(); + return { available_width, available_height }; + } }; enum class FoundUnoccupiedPlace { @@ -348,7 +355,6 @@ private: CSSPixels calculate_limited_max_content_contribution(GridItem const&, GridDimension const) const; CSSPixels containing_block_size_for_item(GridItem const&, GridDimension const) const; - AvailableSpace get_available_space_for_item(GridItem const&) const; CSSPixelRect get_grid_area_rect(GridItem const&) const; From 76482559d0e516f4973493ddab48a50f64c97cce Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 20 Mar 2025 17:50:10 +0100 Subject: [PATCH 138/141] LibWeb: Unify grid item's width and height resolution in GFC It was annoying to maintain two separate but almost identical functions that gradually accumulated small differences over time. This change replaces them with a single function that resolves either width or height, depending on the specified dimension. --- .../LibWeb/Layout/GridFormattingContext.cpp | 450 +++++++++--------- .../LibWeb/Layout/GridFormattingContext.h | 53 ++- 2 files changed, 259 insertions(+), 244 deletions(-) diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index ce7d1e61ea8..e38d7cc8b5a 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Aliaksandr Kalenik + * Copyright (c) 2023-2025, Aliaksandr Kalenik * Copyright (c) 2022-2023, Martin Falisse * * SPDX-License-Identifier: BSD-2-Clause @@ -50,6 +50,44 @@ static Alignment to_alignment(CSS::JustifyContent value) } } +static Alignment to_alignment(CSS::JustifyItems value) +{ + switch (value) { + case CSS::JustifyItems::Baseline: + return Alignment::Baseline; + case CSS::JustifyItems::Center: + return Alignment::Center; + case CSS::JustifyItems::End: + return Alignment::End; + case CSS::JustifyItems::FlexEnd: + return Alignment::End; + case CSS::JustifyItems::FlexStart: + return Alignment::Start; + case CSS::JustifyItems::Legacy: + return Alignment::Normal; + case CSS::JustifyItems::Normal: + return Alignment::Normal; + case CSS::JustifyItems::Safe: + return Alignment::Safe; + case CSS::JustifyItems::SelfEnd: + return Alignment::SelfEnd; + case CSS::JustifyItems::SelfStart: + return Alignment::SelfStart; + case CSS::JustifyItems::Start: + return Alignment::Start; + case CSS::JustifyItems::Stretch: + return Alignment::Stretch; + case CSS::JustifyItems::Unsafe: + return Alignment::Unsafe; + case CSS::JustifyItems::Left: + return Alignment::Start; + case CSS::JustifyItems::Right: + return Alignment::End; + default: + VERIFY_NOT_REACHED(); + } +} + static Alignment to_alignment(CSS::AlignContent value) { switch (value) { @@ -78,6 +116,38 @@ static Alignment to_alignment(CSS::AlignContent value) } } +static Alignment to_alignment(CSS::AlignItems value) +{ + switch (value) { + case CSS::AlignItems::Baseline: + return Alignment::Baseline; + case CSS::AlignItems::Center: + return Alignment::Center; + case CSS::AlignItems::End: + return Alignment::End; + case CSS::AlignItems::FlexEnd: + return Alignment::End; + case CSS::AlignItems::FlexStart: + return Alignment::Start; + case CSS::AlignItems::Normal: + return Alignment::Normal; + case CSS::AlignItems::Safe: + return Alignment::Safe; + case CSS::AlignItems::SelfEnd: + return Alignment::SelfEnd; + case CSS::AlignItems::SelfStart: + return Alignment::SelfStart; + case CSS::AlignItems::Start: + return Alignment::Start; + case CSS::AlignItems::Stretch: + return Alignment::Stretch; + case CSS::AlignItems::Unsafe: + return Alignment::Unsafe; + default: + VERIFY_NOT_REACHED(); + } +} + GridFormattingContext::GridTrack GridFormattingContext::GridTrack::create_from_definition(CSS::ExplicitGridTrack const& definition) { // NOTE: repeat() is expected to be expanded beforehand. @@ -1473,137 +1543,128 @@ void GridFormattingContext::determine_grid_container_height() m_automatic_content_height = total_y; } -CSS::JustifyItems GridFormattingContext::justification_for_item(Box const& box) const +Alignment GridFormattingContext::alignment_for_item(Box const& box, GridDimension dimension) const { - switch (box.computed_values().justify_self()) { - case CSS::JustifySelf::Auto: - return grid_container().computed_values().justify_items(); - case CSS::JustifySelf::End: - return CSS::JustifyItems::End; - case CSS::JustifySelf::Normal: - return CSS::JustifyItems::Normal; - case CSS::JustifySelf::SelfStart: - return CSS::JustifyItems::SelfStart; - case CSS::JustifySelf::SelfEnd: - return CSS::JustifyItems::SelfEnd; - case CSS::JustifySelf::FlexStart: - return CSS::JustifyItems::FlexStart; - case CSS::JustifySelf::FlexEnd: - return CSS::JustifyItems::FlexEnd; - case CSS::JustifySelf::Center: - return CSS::JustifyItems::Center; - case CSS::JustifySelf::Baseline: - return CSS::JustifyItems::Baseline; - case CSS::JustifySelf::Start: - return CSS::JustifyItems::Start; - case CSS::JustifySelf::Stretch: - return CSS::JustifyItems::Stretch; - case CSS::JustifySelf::Safe: - return CSS::JustifyItems::Safe; - case CSS::JustifySelf::Unsafe: - return CSS::JustifyItems::Unsafe; - case CSS::JustifySelf::Left: - return CSS::JustifyItems::Left; - case CSS::JustifySelf::Right: - return CSS::JustifyItems::Right; - default: - VERIFY_NOT_REACHED(); + if (dimension == GridDimension::Column) { + switch (box.computed_values().justify_self()) { + case CSS::JustifySelf::Auto: + return to_alignment(grid_container().computed_values().justify_items()); + case CSS::JustifySelf::End: + return Alignment::End; + case CSS::JustifySelf::Normal: + return Alignment::Normal; + case CSS::JustifySelf::SelfStart: + return Alignment::SelfStart; + case CSS::JustifySelf::SelfEnd: + return Alignment::SelfEnd; + case CSS::JustifySelf::FlexStart: + return Alignment::Start; + case CSS::JustifySelf::FlexEnd: + return Alignment::End; + case CSS::JustifySelf::Center: + return Alignment::Center; + case CSS::JustifySelf::Baseline: + return Alignment::Baseline; + case CSS::JustifySelf::Start: + return Alignment::Start; + case CSS::JustifySelf::Stretch: + return Alignment::Stretch; + case CSS::JustifySelf::Safe: + return Alignment::Safe; + case CSS::JustifySelf::Unsafe: + return Alignment::Unsafe; + case CSS::JustifySelf::Left: + return Alignment::Start; + case CSS::JustifySelf::Right: + return Alignment::End; + default: + VERIFY_NOT_REACHED(); + } } -} - -CSS::AlignItems GridFormattingContext::alignment_for_item(Box const& box) const -{ switch (box.computed_values().align_self()) { case CSS::AlignSelf::Auto: - return grid_container().computed_values().align_items(); + return to_alignment(grid_container().computed_values().align_items()); case CSS::AlignSelf::End: - return CSS::AlignItems::End; + return Alignment::End; case CSS::AlignSelf::Normal: - return CSS::AlignItems::Normal; + return Alignment::Normal; case CSS::AlignSelf::SelfStart: - return CSS::AlignItems::SelfStart; + return Alignment::SelfStart; case CSS::AlignSelf::SelfEnd: - return CSS::AlignItems::SelfEnd; + return Alignment::SelfEnd; case CSS::AlignSelf::FlexStart: - return CSS::AlignItems::FlexStart; + return Alignment::Start; case CSS::AlignSelf::FlexEnd: - return CSS::AlignItems::FlexEnd; + return Alignment::End; case CSS::AlignSelf::Center: - return CSS::AlignItems::Center; + return Alignment::Center; case CSS::AlignSelf::Baseline: - return CSS::AlignItems::Baseline; + return Alignment::Baseline; case CSS::AlignSelf::Start: - return CSS::AlignItems::Start; + return Alignment::Start; case CSS::AlignSelf::Stretch: - return CSS::AlignItems::Stretch; + return Alignment::Stretch; case CSS::AlignSelf::Safe: - return CSS::AlignItems::Safe; + return Alignment::Safe; case CSS::AlignSelf::Unsafe: - return CSS::AlignItems::Unsafe; + return Alignment::Unsafe; default: VERIFY_NOT_REACHED(); } } -void GridFormattingContext::resolve_grid_item_widths() +void GridFormattingContext::resolve_grid_item_sizes(GridDimension dimension) { for (auto& item : m_grid_items) { - CSSPixels containing_block_width = containing_block_size_for_item(item, GridDimension::Column); - CSS::JustifyItems justification = justification_for_item(item.box); + CSSPixels containing_block_size = containing_block_size_for_item(item, dimension); + Alignment alignment = alignment_for_item(item.box, dimension); - auto const& computed_values = item.box->computed_values(); - auto const& computed_width = computed_values.width(); + auto const& preferred_size = item.preferred_size(dimension); struct ItemAlignment { - CSSPixels margin_left; - CSSPixels margin_right; - CSSPixels width; + CSSPixels margin_start; + CSSPixels margin_end; + CSSPixels size; }; - auto try_compute_width = [&item, containing_block_width, justification](CSSPixels a_width, CSS::Size const& computed_width) -> ItemAlignment { - auto const& computed_values = item.box->computed_values(); - + auto try_compute_size = [&item, containing_block_size, alignment, dimension](CSSPixels a_size, CSS::Size const& css_size) -> ItemAlignment { ItemAlignment result = { - .margin_left = item.used_values.margin_left, - .margin_right = item.used_values.margin_right, - .width = a_width + .margin_start = item.used_margin_start(dimension), + .margin_end = item.used_margin_end(dimension), + .size = a_size }; // Auto margins absorb positive free space prior to alignment via the box alignment properties. - auto free_space_left_for_margins = containing_block_width - result.width - item.used_values.border_left - item.used_values.border_right - item.used_values.padding_left - item.used_values.padding_right - item.used_values.margin_left - item.used_values.margin_right; - if (computed_values.margin().left().is_auto() && computed_values.margin().right().is_auto()) { - result.margin_left = free_space_left_for_margins / 2; - result.margin_right = free_space_left_for_margins / 2; - } else if (computed_values.margin().left().is_auto()) { - result.margin_left = free_space_left_for_margins; - } else if (computed_values.margin().right().is_auto()) { - result.margin_right = free_space_left_for_margins; - } else if (computed_width.is_auto() && !item.box->is_replaced_box()) { - result.width += free_space_left_for_margins; + auto free_space_left_for_margins = containing_block_size - result.size - item.used_margin_box_start(dimension) - item.used_margin_box_end(dimension); + if (item.margin_start(dimension).is_auto() && item.margin_end(dimension).is_auto()) { + result.margin_start = free_space_left_for_margins / 2; + result.margin_end = free_space_left_for_margins / 2; + } else if (item.margin_start(dimension).is_auto()) { + result.margin_start = free_space_left_for_margins; + } else if (item.margin_end(dimension).is_auto()) { + result.margin_end = free_space_left_for_margins; + } else if (css_size.is_auto() && !item.box->is_replaced_box()) { + result.size += free_space_left_for_margins; } - auto free_space_left_for_alignment = containing_block_width - a_width - item.used_values.border_left - item.used_values.border_right - item.used_values.padding_left - item.used_values.padding_right - item.used_values.margin_left - item.used_values.margin_right; - switch (justification) { - case CSS::JustifyItems::Normal: + auto free_space_left_for_alignment = containing_block_size - a_size - item.used_margin_box_start(dimension) - item.used_margin_box_end(dimension); + switch (alignment) { + case Alignment::Normal: + case Alignment::Stretch: break; - case CSS::JustifyItems::Stretch: + case Alignment::Center: + result.margin_start += free_space_left_for_alignment / 2; + result.margin_end += free_space_left_for_alignment / 2; + result.size = a_size; break; - case CSS::JustifyItems::Center: - result.margin_left += free_space_left_for_alignment / 2; - result.margin_right += free_space_left_for_alignment / 2; - result.width = a_width; + case Alignment::Baseline: + case Alignment::Start: + result.margin_end += free_space_left_for_alignment; + result.size = a_size; break; - case CSS::JustifyItems::Start: - case CSS::JustifyItems::FlexStart: - case CSS::JustifyItems::Left: - result.margin_right += free_space_left_for_alignment; - result.width = a_width; - break; - case CSS::JustifyItems::End: - case CSS::JustifyItems::FlexEnd: - case CSS::JustifyItems::Right: - result.margin_left += free_space_left_for_alignment; - result.width = a_width; + case Alignment::End: + result.margin_start += free_space_left_for_alignment; + result.size = a_size; break; default: break; @@ -1612,137 +1673,64 @@ void GridFormattingContext::resolve_grid_item_widths() return result; }; + AvailableSpace available_space { + AvailableSize::make_definite(containing_block_size_for_item(item, GridDimension::Column)), + AvailableSize::make_definite(containing_block_size_for_item(item, GridDimension::Row)) + }; + + auto calculate_inner_size = [this, &item, dimension, available_space](CSS::Size const& size) { + if (dimension == GridDimension::Column) + return calculate_inner_width(item.box, available_space.width, size); + return calculate_inner_height(item.box, available_space, size); + }; + + auto tentative_size_for_replaced_element = [this, &item, dimension, available_space](CSS::Size const& size) { + if (dimension == GridDimension::Column) + return tentative_width_for_replaced_element(item.box, size, available_space); + return tentative_height_for_replaced_element(item.box, size, available_space); + }; + ItemAlignment used_alignment; - AvailableSpace available_space { AvailableSize::make_definite(containing_block_width), AvailableSize::make_indefinite() }; if (item.box->is_replaced_box() && item.box->has_natural_width()) { - auto width = tentative_width_for_replaced_element(item.box, computed_values.width(), available_space); - used_alignment = try_compute_width(width, computed_width); + auto width = tentative_size_for_replaced_element(preferred_size); + used_alignment = try_compute_size(width, item.preferred_size(dimension)); } else { - if (computed_width.is_auto() || computed_width.is_fit_content()) { - auto fit_content_width = calculate_fit_content_width(item.box, available_space); - used_alignment = try_compute_width(fit_content_width, computed_width); - used_alignment = try_compute_width(calculate_fit_content_width(item.box, available_space), computed_width); + if (preferred_size.is_auto() || preferred_size.is_fit_content()) { + auto fit_content_size = dimension == GridDimension::Column ? calculate_fit_content_width(item.box, available_space) : calculate_fit_content_height(item.box, available_space); + used_alignment = try_compute_size(fit_content_size, preferred_size); } else { - auto width_px = calculate_inner_width(item.box, available_space.width, computed_width); - used_alignment = try_compute_width(width_px, computed_width); + auto size_px = calculate_inner_size(preferred_size); + used_alignment = try_compute_size(size_px, preferred_size); } } - if (!should_treat_max_width_as_none(item.box, m_available_space->width)) { - auto max_width_px = calculate_inner_width(item.box, available_space.width, computed_values.max_width()); - auto max_width_alignment = try_compute_width(max_width_px, computed_values.max_width()); - if (used_alignment.width > max_width_alignment.width) { + bool should_treat_maximum_size_as_none = dimension == GridDimension::Column ? should_treat_max_width_as_none(item.box, available_space.width) : should_treat_max_height_as_none(item.box, available_space.height); + if (!should_treat_maximum_size_as_none) { + auto const& maximum_size = item.maximum_size(dimension); + auto max_size_px = calculate_inner_size(maximum_size); + auto max_width_alignment = try_compute_size(max_size_px, maximum_size); + if (used_alignment.size > max_width_alignment.size) { used_alignment = max_width_alignment; } } - if (!computed_values.min_width().is_auto()) { - auto min_width_px = calculate_inner_width(item.box, available_space.width, computed_values.min_width()); - auto min_width_alignment = try_compute_width(min_width_px, computed_values.min_width()); - if (used_alignment.width < min_width_alignment.width) { - used_alignment = min_width_alignment; + auto const& minimum_size = item.minimum_size(dimension); + if (!minimum_size.is_auto()) { + auto min_size_alignment = try_compute_size(calculate_inner_size(minimum_size), minimum_size); + if (used_alignment.size < min_size_alignment.size) { + used_alignment = min_size_alignment; } } - item.used_values.margin_left = used_alignment.margin_left; - item.used_values.margin_right = used_alignment.margin_right; - item.used_values.set_content_width(used_alignment.width); - } -} - -void GridFormattingContext::resolve_grid_item_heights() -{ - for (auto& item : m_grid_items) { - CSSPixels containing_block_height = containing_block_size_for_item(item, GridDimension::Row); - CSS::AlignItems alignment = alignment_for_item(item.box); - - auto const& computed_values = item.box->computed_values(); - auto const& computed_height = computed_values.height(); - - struct ItemAlignment { - CSSPixels margin_top; - CSSPixels margin_bottom; - CSSPixels height; - }; - - auto try_compute_height = [&item, containing_block_height, alignment](CSSPixels a_height) -> ItemAlignment { - auto const& computed_values = item.box->computed_values(); - - ItemAlignment result = { - .margin_top = item.used_values.margin_top, - .margin_bottom = item.used_values.margin_bottom, - .height = a_height - }; - - CSSPixels height = a_height; - auto underflow_px = containing_block_height - height - item.used_values.border_top - item.used_values.border_bottom - item.used_values.padding_top - item.used_values.padding_bottom - item.used_values.margin_top - item.used_values.margin_bottom; - if (computed_values.margin().top().is_auto() && computed_values.margin().bottom().is_auto()) { - auto half_of_the_underflow = underflow_px / 2; - result.margin_top = half_of_the_underflow; - result.margin_bottom = half_of_the_underflow; - } else if (computed_values.margin().top().is_auto()) { - result.margin_top = underflow_px; - } else if (computed_values.margin().bottom().is_auto()) { - result.margin_bottom = underflow_px; - } else if (computed_values.height().is_auto() && !item.box->is_replaced_box()) { - height += underflow_px; - } - - switch (alignment) { - case CSS::AlignItems::Baseline: - // FIXME: Not implemented - break; - case CSS::AlignItems::Stretch: - case CSS::AlignItems::Normal: - result.height = height; - break; - case CSS::AlignItems::Start: - case CSS::AlignItems::FlexStart: - case CSS::AlignItems::SelfStart: - result.margin_bottom += underflow_px; - break; - case CSS::AlignItems::End: - case CSS::AlignItems::SelfEnd: - case CSS::AlignItems::FlexEnd: - result.margin_top += underflow_px; - break; - case CSS::AlignItems::Center: - result.margin_top += underflow_px / 2; - result.margin_bottom += underflow_px / 2; - break; - default: - break; - } - - return result; - }; - - ItemAlignment used_alignment; - if (computed_height.is_auto()) { - used_alignment = try_compute_height(calculate_fit_content_height(item.box, item.available_space())); - } else if (computed_height.is_fit_content()) { - used_alignment = try_compute_height(calculate_fit_content_height(item.box, item.available_space())); + if (dimension == GridDimension::Column) { + item.used_values.margin_left = used_alignment.margin_start; + item.used_values.margin_right = used_alignment.margin_end; + item.used_values.set_content_width(used_alignment.size); } else { - used_alignment = try_compute_height(computed_height.to_px(grid_container(), containing_block_height)); + item.used_values.margin_top = used_alignment.margin_start; + item.used_values.margin_bottom = used_alignment.margin_end; + item.used_values.set_content_height(used_alignment.size); } - - if (!should_treat_max_height_as_none(item.box, m_available_space->height)) { - auto max_height_alignment = try_compute_height(computed_values.max_height().to_px(grid_container(), containing_block_height)); - if (used_alignment.height > max_height_alignment.height) { - used_alignment = max_height_alignment; - } - } - - if (!computed_values.min_height().is_auto()) { - auto min_height_alignment = try_compute_height(computed_values.min_height().to_px(grid_container(), containing_block_height)); - if (used_alignment.height < min_height_alignment.height) { - used_alignment = min_height_alignment; - } - } - - item.used_values.margin_top = used_alignment.margin_top; - item.used_values.margin_bottom = used_alignment.margin_bottom; - item.used_values.set_content_height(used_alignment.height); } } @@ -2024,7 +2012,7 @@ void GridFormattingContext::run(AvailableSpace const& available_space) // Once the sizes of column tracks, which determine the widths of the grid areas forming the containing blocks // for grid items, ara calculated, it becomes possible to determine the final widths of the grid items. - resolve_grid_item_widths(); + resolve_grid_item_sizes(GridDimension::Column); // Do the first pass of resolving grid items box metrics to compute values that are independent of a track height resolve_items_box_metrics(GridDimension::Row); @@ -2034,7 +2022,7 @@ void GridFormattingContext::run(AvailableSpace const& available_space) // Do the second pass of resolving box metrics to compute values that depend on a track height resolve_items_box_metrics(GridDimension::Row); - resolve_grid_item_heights(); + resolve_grid_item_sizes(GridDimension::Row); determine_grid_container_height(); @@ -2057,7 +2045,7 @@ void GridFormattingContext::run(AvailableSpace const& available_space) resolve_items_box_metrics(GridDimension::Row); - resolve_grid_item_heights(); + resolve_grid_item_sizes(GridDimension::Row); determine_grid_container_height(); } @@ -2145,22 +2133,18 @@ void GridFormattingContext::layout_absolutely_positioned_element(Box const& box) if (computed_values.inset().left().is_auto() && computed_values.inset().right().is_auto()) { auto width_left_for_alignment = grid_area_rect.width() - box_state.margin_box_width(); - switch (justification_for_item(box)) { - case CSS::JustifyItems::Normal: - case CSS::JustifyItems::Stretch: + switch (alignment_for_item(box, GridDimension::Column)) { + case Alignment::Normal: + case Alignment::Stretch: break; - case CSS::JustifyItems::Center: + case Alignment::Center: box_state.inset_left = width_left_for_alignment / 2; box_state.inset_right = width_left_for_alignment / 2; break; - case CSS::JustifyItems::Start: - case CSS::JustifyItems::FlexStart: - case CSS::JustifyItems::Left: + case Alignment::Start: box_state.inset_right = width_left_for_alignment; break; - case CSS::JustifyItems::End: - case CSS::JustifyItems::FlexEnd: - case CSS::JustifyItems::Right: + case Alignment::End: box_state.inset_left = width_left_for_alignment; break; default: @@ -2170,24 +2154,22 @@ void GridFormattingContext::layout_absolutely_positioned_element(Box const& box) if (computed_values.inset().top().is_auto() && computed_values.inset().bottom().is_auto()) { auto height_left_for_alignment = grid_area_rect.height() - box_state.margin_box_height(); - switch (alignment_for_item(box)) { - case CSS::AlignItems::Baseline: + switch (alignment_for_item(box, GridDimension::Row)) { + case Alignment::Baseline: // FIXME: Not implemented - case CSS::AlignItems::Stretch: - case CSS::AlignItems::Normal: + case Alignment::Stretch: + case Alignment::Normal: break; - case CSS::AlignItems::Start: - case CSS::AlignItems::FlexStart: - case CSS::AlignItems::SelfStart: + case Alignment::Start: + case Alignment::SelfStart: box_state.inset_bottom = height_left_for_alignment; break; - case CSS::AlignItems::End: - case CSS::AlignItems::SelfEnd: - case CSS::AlignItems::FlexEnd: { + case Alignment::End: + case Alignment::SelfEnd: { box_state.inset_top = height_left_for_alignment; break; } - case CSS::AlignItems::Center: + case Alignment::Center: box_state.inset_top = height_left_for_alignment / 2; box_state.inset_bottom = height_left_for_alignment / 2; break; diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.h b/Libraries/LibWeb/Layout/GridFormattingContext.h index 755fa85b6b0..0cbd094ae1d 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Aliaksandr Kalenik + * Copyright (c) 2023-2025, Aliaksandr Kalenik * Copyright (c) 2022-2023, Martin Falisse * * SPDX-License-Identifier: BSD-2-Clause @@ -18,14 +18,19 @@ enum class GridDimension { }; enum class Alignment { - Normal, - SpaceBetween, - SpaceAround, - SpaceEvenly, + Baseline, Center, - Start, End, + Normal, + Safe, + SelfEnd, + SelfStart, + SpaceAround, + SpaceBetween, + SpaceEvenly, + Start, Stretch, + Unsafe, }; struct GridPosition { @@ -89,6 +94,36 @@ struct GridItem { return dimension == GridDimension::Column ? computed_values().width() : computed_values().height(); } + CSS::LengthPercentage const& margin_start(GridDimension dimension) const + { + return dimension == GridDimension::Column ? computed_values().margin().left() : computed_values().margin().top(); + } + + CSS::LengthPercentage const& margin_end(GridDimension dimension) const + { + return dimension == GridDimension::Column ? computed_values().margin().right() : computed_values().margin().bottom(); + } + + CSSPixels used_margin_box_start(GridDimension dimension) const + { + return dimension == GridDimension::Column ? used_values.margin_box_left() : used_values.margin_box_top(); + } + + CSSPixels used_margin_box_end(GridDimension dimension) const + { + return dimension == GridDimension::Column ? used_values.margin_box_right() : used_values.margin_box_bottom(); + } + + CSSPixels used_margin_start(GridDimension dimension) const + { + return dimension == GridDimension::Column ? used_values.margin_left : used_values.margin_top; + } + + CSSPixels used_margin_end(GridDimension dimension) const + { + return dimension == GridDimension::Column ? used_values.margin_right : used_values.margin_bottom; + } + AvailableSpace available_space() const { auto available_width = used_values.has_definite_width() ? AvailableSize::make_definite(used_values.content_width()) : AvailableSize::make_indefinite(); @@ -158,8 +193,7 @@ public: Box const& grid_container() const { return context_box(); } private: - CSS::JustifyItems justification_for_item(Box const& box) const; - CSS::AlignItems alignment_for_item(Box const& box) const; + Alignment alignment_for_item(Box const& box, GridDimension dimension) const; void resolve_items_box_metrics(GridDimension const dimension); @@ -288,8 +322,7 @@ private: void layout_absolutely_positioned_element(Box const&); virtual void parent_context_did_dimension_child_root_box() override; - void resolve_grid_item_widths(); - void resolve_grid_item_heights(); + void resolve_grid_item_sizes(GridDimension dimension); void resolve_track_spacing(GridDimension const dimension); From 9ec986753a07137db9931d7b50793f3a187dcad3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 21 Mar 2025 00:20:21 +0100 Subject: [PATCH 139/141] LibWeb: Delete const from GridDimension function parameters in GFC Using const on primitive type was only adding unnecessary noise. --- .../LibWeb/Layout/GridFormattingContext.cpp | 58 +++++++++---------- .../LibWeb/Layout/GridFormattingContext.h | 50 ++++++++-------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Libraries/LibWeb/Layout/GridFormattingContext.cpp index e38d7cc8b5a..f169bd4cc48 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -292,7 +292,7 @@ int GridFormattingContext::count_of_repeated_auto_fill_or_fit_tracks(GridDimensi // floor be 1px. } -GridFormattingContext::PlacementPosition GridFormattingContext::resolve_grid_position(Box const& child_box, GridDimension const dimension) +GridFormattingContext::PlacementPosition GridFormattingContext::resolve_grid_position(Box const& child_box, GridDimension dimension) { auto const& computed_values = child_box.computed_values(); auto const& placement_start = dimension == GridDimension::Row ? computed_values.grid_row_start() : computed_values.grid_column_start(); @@ -682,7 +682,7 @@ void GridFormattingContext::initialize_gap_tracks(AvailableSpace const& availabl } } -void GridFormattingContext::initialize_track_sizes(GridDimension const dimension) +void GridFormattingContext::initialize_track_sizes(GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#algo-init // 12.4. Initialize Track Sizes @@ -718,7 +718,7 @@ void GridFormattingContext::initialize_track_sizes(GridDimension const dimension } } -void GridFormattingContext::resolve_intrinsic_track_sizes(GridDimension const dimension) +void GridFormattingContext::resolve_intrinsic_track_sizes(GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#algo-content // 12.5. Resolve Intrinsic Track Sizes @@ -916,7 +916,7 @@ void GridFormattingContext::distribute_extra_space_across_spanned_tracks_growth_ } } -void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(GridDimension const dimension, size_t span) +void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(GridDimension dimension, size_t span) { auto& available_size = dimension == GridDimension::Column ? m_available_space->width : m_available_space->height; auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; @@ -1031,7 +1031,7 @@ void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossin } } -void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(GridDimension const dimension) +void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(GridDimension dimension) { auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; for (auto& item : m_grid_items) { @@ -1068,7 +1068,7 @@ void GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossin } } -void GridFormattingContext::maximize_tracks_using_available_size(AvailableSpace const& available_space, GridDimension const dimension) +void GridFormattingContext::maximize_tracks_using_available_size(AvailableSpace const& available_space, GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#algo-grow-tracks // 12.6. Maximize Tracks @@ -1106,7 +1106,7 @@ void GridFormattingContext::maximize_tracks_using_available_size(AvailableSpace } } -void GridFormattingContext::maximize_tracks(GridDimension const dimension) +void GridFormattingContext::maximize_tracks(GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#algo-grow-tracks // 12.6. Maximize Tracks @@ -1148,7 +1148,7 @@ void GridFormattingContext::maximize_tracks(GridDimension const dimension) } } -void GridFormattingContext::expand_flexible_tracks(GridDimension const dimension) +void GridFormattingContext::expand_flexible_tracks(GridDimension dimension) { // https://drafts.csswg.org/css-grid/#algo-flex-tracks // 12.7. Expand Flexible Tracks @@ -1265,7 +1265,7 @@ void GridFormattingContext::expand_flexible_tracks(GridDimension const dimension } } -void GridFormattingContext::stretch_auto_tracks(GridDimension const dimension) +void GridFormattingContext::stretch_auto_tracks(GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#algo-stretch // 12.8. Stretch auto Tracks @@ -1306,7 +1306,7 @@ void GridFormattingContext::stretch_auto_tracks(GridDimension const dimension) } } -void GridFormattingContext::run_track_sizing(GridDimension const dimension) +void GridFormattingContext::run_track_sizing(GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#algo-track-sizing // 12.3. Track Sizing Algorithm @@ -1734,7 +1734,7 @@ void GridFormattingContext::resolve_grid_item_sizes(GridDimension dimension) } } -void GridFormattingContext::resolve_track_spacing(GridDimension const dimension) +void GridFormattingContext::resolve_track_spacing(GridDimension dimension) { auto is_column_dimension = dimension == GridDimension::Column; @@ -1787,7 +1787,7 @@ void GridFormattingContext::resolve_track_spacing(GridDimension const dimension) } } -void GridFormattingContext::resolve_items_box_metrics(GridDimension const dimension) +void GridFormattingContext::resolve_items_box_metrics(GridDimension dimension) { for (auto& item : m_grid_items) { auto& computed_values = item.box->computed_values(); @@ -1815,7 +1815,7 @@ void GridFormattingContext::resolve_items_box_metrics(GridDimension const dimens } } -void GridFormattingContext::collapse_auto_fit_tracks_if_needed(GridDimension const dimension) +void GridFormattingContext::collapse_auto_fit_tracks_if_needed(GridDimension dimension) { // https://www.w3.org/TR/css-grid-2/#auto-repeat // The auto-fit keyword behaves the same as auto-fill, except that after grid item placement any @@ -1840,7 +1840,7 @@ CSSPixelRect GridFormattingContext::get_grid_area_rect(GridItem const& grid_item { CSSPixelRect area_rect; - auto place_into_track = [&](GridDimension const dimension) { + auto place_into_track = [&](GridDimension dimension) { auto const& tracks_and_gaps = dimension == GridDimension::Column ? m_grid_columns_and_gaps : m_grid_rows_and_gaps; auto resolved_span = grid_item.span(dimension) * 2; @@ -1891,7 +1891,7 @@ CSSPixelRect GridFormattingContext::get_grid_area_rect(GridItem const& grid_item } }; - auto place_into_track_formed_by_last_line_and_grid_container_padding_edge = [&](GridDimension const dimension) { + auto place_into_track_formed_by_last_line_and_grid_container_padding_edge = [&](GridDimension dimension) { VERIFY(grid_item.box->is_absolutely_positioned()); auto const& tracks_and_gaps = dimension == GridDimension::Column ? m_grid_columns_and_gaps : m_grid_rows_and_gaps; CSSPixels offset = 0; @@ -2248,7 +2248,7 @@ bool GridFormattingContext::is_auto_positioned_track(CSS::GridTrackPlacement con return grid_track_start.is_auto_positioned() && grid_track_end.is_auto_positioned(); } -AvailableSize GridFormattingContext::get_free_space(AvailableSpace const& available_space, GridDimension const dimension) const +AvailableSize GridFormattingContext::get_free_space(AvailableSpace const& available_space, GridDimension dimension) const { // https://www.w3.org/TR/css-grid-2/#algo-terms // free space: Equal to the available grid space minus the sum of the base sizes of all the grid @@ -2343,7 +2343,7 @@ int GridItem::gap_adjusted_column() const return column.value() * 2; } -CSSPixels GridFormattingContext::calculate_grid_container_maximum_size(GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_grid_container_maximum_size(GridDimension dimension) const { auto const& computed_values = grid_container().computed_values(); if (dimension == GridDimension::Column) @@ -2351,7 +2351,7 @@ CSSPixels GridFormattingContext::calculate_grid_container_maximum_size(GridDimen return calculate_inner_height(grid_container(), m_available_space.value(), computed_values.max_height()); } -CSSPixels GridFormattingContext::calculate_min_content_size(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_min_content_size(GridItem const& item, GridDimension dimension) const { if (dimension == GridDimension::Column) { return calculate_min_content_width(item.box); @@ -2359,7 +2359,7 @@ CSSPixels GridFormattingContext::calculate_min_content_size(GridItem const& item return calculate_min_content_height(item.box, item.available_space().width.to_px_or_zero()); } -CSSPixels GridFormattingContext::calculate_max_content_size(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_max_content_size(GridItem const& item, GridDimension dimension) const { if (dimension == GridDimension::Column) { return calculate_max_content_width(item.box); @@ -2367,7 +2367,7 @@ CSSPixels GridFormattingContext::calculate_max_content_size(GridItem const& item return calculate_max_content_height(item.box, item.available_space().width.to_px_or_zero()); } -CSSPixels GridFormattingContext::containing_block_size_for_item(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::containing_block_size_for_item(GridItem const& item, GridDimension dimension) const { CSSPixels containing_block_size = 0; for_each_spanned_track_by_item(item, dimension, [&](GridTrack const& track) { @@ -2376,7 +2376,7 @@ CSSPixels GridFormattingContext::containing_block_size_for_item(GridItem const& return containing_block_size; } -CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem const& item, GridDimension dimension) const { auto available_space_for_item = item.available_space(); @@ -2405,7 +2405,7 @@ CSSPixels GridFormattingContext::calculate_min_content_contribution(GridItem con return min(item.add_margin_box_sizes(height, dimension), maxium_size); } -CSSPixels GridFormattingContext::calculate_max_content_contribution(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_max_content_contribution(GridItem const& item, GridDimension dimension) const { auto available_space_for_item = item.available_space(); @@ -2432,7 +2432,7 @@ CSSPixels GridFormattingContext::calculate_max_content_contribution(GridItem con return min(result, maxium_size); } -CSSPixels GridFormattingContext::calculate_limited_min_content_contribution(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_limited_min_content_contribution(GridItem const& item, GridDimension dimension) const { // The limited min-content contribution of an item is its min-content contribution, // limited by the max track sizing function (which could be the argument to a fit-content() track @@ -2463,7 +2463,7 @@ CSSPixels GridFormattingContext::calculate_limited_min_content_contribution(Grid return min_content_contribution; } -CSSPixels GridFormattingContext::calculate_limited_max_content_contribution(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_limited_max_content_contribution(GridItem const& item, GridDimension dimension) const { // The limited max-content contribution of an item is its max-content contribution, // limited by the max track sizing function (which could be the argument to a fit-content() track @@ -2484,7 +2484,7 @@ CSSPixels GridFormattingContext::calculate_limited_max_content_contribution(Grid return max_content_contribution; } -CSSPixels GridFormattingContext::content_size_suggestion(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::content_size_suggestion(GridItem const& item, GridDimension dimension) const { // The content size suggestion is the min-content size in the relevant axis // FIXME: clamped, if it has a preferred aspect ratio, by any definite opposite-axis minimum and maximum sizes @@ -2492,7 +2492,7 @@ CSSPixels GridFormattingContext::content_size_suggestion(GridItem const& item, G return calculate_min_content_size(item, dimension); } -Optional GridFormattingContext::specified_size_suggestion(GridItem const& item, GridDimension const dimension) const +Optional GridFormattingContext::specified_size_suggestion(GridItem const& item, GridDimension dimension) const { // https://www.w3.org/TR/css-grid-1/#specified-size-suggestion // If the item’s preferred size in the relevant axis is definite, then the specified size suggestion is that size. @@ -2527,7 +2527,7 @@ Optional GridFormattingContext::transferred_size_suggestion(GridItem return {}; } -CSSPixels GridFormattingContext::content_based_minimum_size(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::content_based_minimum_size(GridItem const& item, GridDimension dimension) const { // https://www.w3.org/TR/css-grid-1/#content-based-minimum-size @@ -2578,7 +2578,7 @@ CSSPixels GridFormattingContext::content_based_minimum_size(GridItem const& item return result; } -CSSPixels GridFormattingContext::automatic_minimum_size(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::automatic_minimum_size(GridItem const& item, GridDimension dimension) const { // To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size // in a given axis is the content-based minimum size if all of the following are true: @@ -2608,7 +2608,7 @@ CSSPixels GridFormattingContext::automatic_minimum_size(GridItem const& item, Gr return 0; } -CSSPixels GridFormattingContext::calculate_minimum_contribution(GridItem const& item, GridDimension const dimension) const +CSSPixels GridFormattingContext::calculate_minimum_contribution(GridItem const& item, GridDimension dimension) const { // The minimum contribution of an item is the smallest outer size it can have. // Specifically, if the item’s computed preferred size behaves as auto or depends on the size of its diff --git a/Libraries/LibWeb/Layout/GridFormattingContext.h b/Libraries/LibWeb/Layout/GridFormattingContext.h index 0cbd094ae1d..16f6a2dc486 100644 --- a/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -49,12 +49,12 @@ struct GridItem { Optional column; Optional column_span; - [[nodiscard]] size_t span(GridDimension const dimension) const + [[nodiscard]] size_t span(GridDimension dimension) const { return dimension == GridDimension::Column ? column_span.value() : row_span.value(); } - [[nodiscard]] int raw_position(GridDimension const dimension) const + [[nodiscard]] int raw_position(GridDimension dimension) const { return dimension == GridDimension::Column ? column.value() : row.value(); } @@ -66,7 +66,7 @@ struct GridItem { return used_values.margin_box_top() + content_size + used_values.margin_box_bottom(); } - [[nodiscard]] int gap_adjusted_position(GridDimension const dimension) const + [[nodiscard]] int gap_adjusted_position(GridDimension dimension) const { return dimension == GridDimension::Column ? gap_adjusted_column() : gap_adjusted_row(); } @@ -195,7 +195,7 @@ public: private: Alignment alignment_for_item(Box const& box, GridDimension dimension) const; - void resolve_items_box_metrics(GridDimension const dimension); + void resolve_items_box_metrics(GridDimension dimension); CSSPixels m_automatic_content_height { 0 }; @@ -243,7 +243,7 @@ private: Vector m_grid_rows; Vector m_grid_columns; - bool has_gaps(GridDimension const dimension) const + bool has_gaps(GridDimension dimension) const { if (dimension == GridDimension::Column) { return !grid_container().computed_values().column_gap().has(); @@ -253,7 +253,7 @@ private: } template - void for_each_spanned_track_by_item(GridItem const& item, GridDimension const dimension, Callback callback) + void for_each_spanned_track_by_item(GridItem const& item, GridDimension dimension, Callback callback) { auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; auto& gaps = dimension == GridDimension::Column ? m_column_gap_tracks : m_row_gap_tracks; @@ -277,7 +277,7 @@ private: } template - void for_each_spanned_track_by_item(GridItem const& item, GridDimension const dimension, Callback callback) const + void for_each_spanned_track_by_item(GridItem const& item, GridDimension dimension, Callback callback) const { auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; auto& gaps = dimension == GridDimension::Column ? m_column_gap_tracks : m_row_gap_tracks; @@ -324,9 +324,9 @@ private: void resolve_grid_item_sizes(GridDimension dimension); - void resolve_track_spacing(GridDimension const dimension); + void resolve_track_spacing(GridDimension dimension); - AvailableSize get_free_space(AvailableSpace const&, GridDimension const) const; + AvailableSize get_free_space(AvailableSpace const&, GridDimension) const; Optional get_line_index_by_line_name(GridDimension dimension, String const&); CSSPixels resolve_definite_track_size(CSS::GridSize const&, AvailableSpace const&); @@ -339,7 +339,7 @@ private: int end { 0 }; size_t span { 1 }; }; - PlacementPosition resolve_grid_position(Box const& child_box, GridDimension const dimension); + PlacementPosition resolve_grid_position(Box const& child_box, GridDimension dimension); void place_grid_items(); void place_item_with_row_and_column_position(Box const& child_box); @@ -352,7 +352,7 @@ private: void initialize_grid_tracks_for_columns_and_rows(); void initialize_gap_tracks(AvailableSpace const&); - void collapse_auto_fit_tracks_if_needed(GridDimension const); + void collapse_auto_fit_tracks_if_needed(GridDimension); enum class SpaceDistributionPhase { AccommodateMinimumContribution, @@ -376,27 +376,27 @@ private: void stretch_auto_tracks(GridDimension); void run_track_sizing(GridDimension); - CSSPixels calculate_grid_container_maximum_size(GridDimension const) const; + CSSPixels calculate_grid_container_maximum_size(GridDimension) const; - CSSPixels calculate_min_content_size(GridItem const&, GridDimension const) const; - CSSPixels calculate_max_content_size(GridItem const&, GridDimension const) const; + CSSPixels calculate_min_content_size(GridItem const&, GridDimension) const; + CSSPixels calculate_max_content_size(GridItem const&, GridDimension) const; - CSSPixels calculate_min_content_contribution(GridItem const&, GridDimension const) const; - CSSPixels calculate_max_content_contribution(GridItem const&, GridDimension const) const; + CSSPixels calculate_min_content_contribution(GridItem const&, GridDimension) const; + CSSPixels calculate_max_content_contribution(GridItem const&, GridDimension) const; - CSSPixels calculate_limited_min_content_contribution(GridItem const&, GridDimension const) const; - CSSPixels calculate_limited_max_content_contribution(GridItem const&, GridDimension const) const; + CSSPixels calculate_limited_min_content_contribution(GridItem const&, GridDimension) const; + CSSPixels calculate_limited_max_content_contribution(GridItem const&, GridDimension) const; - CSSPixels containing_block_size_for_item(GridItem const&, GridDimension const) const; + CSSPixels containing_block_size_for_item(GridItem const&, GridDimension) const; CSSPixelRect get_grid_area_rect(GridItem const&) const; - CSSPixels content_size_suggestion(GridItem const&, GridDimension const) const; - Optional specified_size_suggestion(GridItem const&, GridDimension const) const; - Optional transferred_size_suggestion(GridItem const&, GridDimension const) const; - CSSPixels content_based_minimum_size(GridItem const&, GridDimension const) const; - CSSPixels automatic_minimum_size(GridItem const&, GridDimension const) const; - CSSPixels calculate_minimum_contribution(GridItem const&, GridDimension const) const; + CSSPixels content_size_suggestion(GridItem const&, GridDimension) const; + Optional specified_size_suggestion(GridItem const&, GridDimension) const; + Optional transferred_size_suggestion(GridItem const&, GridDimension) const; + CSSPixels content_based_minimum_size(GridItem const&, GridDimension) const; + CSSPixels automatic_minimum_size(GridItem const&, GridDimension) const; + CSSPixels calculate_minimum_contribution(GridItem const&, GridDimension) const; }; } From ed62aa62249a45b104088a0850f356d2baeaa8a9 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Fri, 21 Mar 2025 14:51:13 +0000 Subject: [PATCH 140/141] =?UTF-8?q?Revert=20"LibJS:=20Reduce=20number=20of?= =?UTF-8?q?=20proxy=20traps=20called=20during=20for..in=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …iteration" This reverts commit 357eeba49c229df9b2854e85fa540e5ee182f130. --- Libraries/LibJS/Bytecode/Interpreter.cpp | 99 +++++++++---------- .../builtins/Proxy/for-in-iteration-traps.js | 40 -------- 2 files changed, 49 insertions(+), 90 deletions(-) delete mode 100644 Libraries/LibJS/Tests/builtins/Proxy/for-in-iteration-traps.js diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index c8d9e01ff80..90edbe4a3a6 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -37,6 +37,30 @@ #include #include +namespace JS { + +struct PropertyKeyAndEnumerableFlag { + JS::PropertyKey key; + bool enumerable { false }; +}; + +} + +namespace AK { +template<> +struct Traits : public DefaultTraits { + static unsigned hash(JS::PropertyKeyAndEnumerableFlag const& entry) + { + return Traits::hash(entry.key); + } + + static bool equals(JS::PropertyKeyAndEnumerableFlag const& a, JS::PropertyKeyAndEnumerableFlag const& b) + { + return Traits::equals(a.key, b.key); + } +}; +} + namespace JS::Bytecode { bool g_dump_bytecode = false; @@ -1694,16 +1718,6 @@ inline ThrowCompletionOr delete_by_value_with_this(Bytecode::Interpreter& return Value(TRY(reference.delete_(vm))); } -class PropertyNameIteratorState final : public GC::Cell { - GC_CELL(PropertyNameIteratorState, GC::Cell); - GC_DECLARE_ALLOCATOR(PropertyNameIteratorState); - -public: - HashTable non_enumerable_property_keys; -}; - -GC_DEFINE_ALLOCATOR(PropertyNameIteratorState); - // 14.7.5.9 EnumerateObjectProperties ( O ), https://tc39.es/ecma262/#sec-enumerate-object-properties inline ThrowCompletionOr get_object_property_iterator(VM& vm, Value value) { @@ -1724,74 +1738,60 @@ inline ThrowCompletionOr get_object_property_iterator(VM& vm, Value val // Note: While the spec doesn't explicitly require these to be ordered, it says that the values should be retrieved via OwnPropertyKeys, // so we just keep the order consistent anyway. - struct ObjectWithProperties { - WeakPtr object; - OrderedHashTable property_names; - }; - Vector objects_with_properties; - + OrderedHashTable properties; HashTable> seen_objects; - HashTable seen_property_keys; // Collect all keys immediately (invariant no. 5) for (auto object_to_check = GC::Ptr { object.ptr() }; object_to_check && !seen_objects.contains(*object_to_check); object_to_check = TRY(object_to_check->internal_get_prototype_of())) { seen_objects.set(*object_to_check); - objects_with_properties.prepend(ObjectWithProperties { - .object = *object_to_check, - .property_names = {}, - }); - auto& properties = objects_with_properties.first().property_names; for (auto& key : TRY(object_to_check->internal_own_property_keys())) { if (key.is_symbol()) continue; - auto property_key = TRY(PropertyKey::from_value(vm, key)); + // NOTE: If there is a non-enumerable property higher up the prototype chain with the same key, + // we mustn't include this property even if it's enumerable (invariant no. 5 and 6) + // This is achieved with the PropertyKeyAndEnumerableFlag struct, which doesn't consider + // the enumerable flag when comparing keys. + PropertyKeyAndEnumerableFlag new_entry { + .key = TRY(PropertyKey::from_value(vm, key)), + .enumerable = false, + }; - if (seen_property_keys.set(property_key, AK::HashSetExistingEntryBehavior::Keep) == HashSetResult::KeptExistingEntry) + if (properties.contains(new_entry)) continue; - properties.set(property_key); + auto descriptor = TRY(object_to_check->internal_get_own_property(new_entry.key)); + if (!descriptor.has_value()) + continue; + + new_entry.enumerable = *descriptor->enumerable; + properties.set(move(new_entry), AK::HashSetExistingEntryBehavior::Keep); } } + properties.remove_all_matching([&](auto& entry) { return !entry.enumerable; }); + auto& realm = *vm.current_realm(); auto result_object = Object::create_with_premade_shape(realm.intrinsics().iterator_result_object_shape()); auto value_offset = realm.intrinsics().iterator_result_object_value_offset(); auto done_offset = realm.intrinsics().iterator_result_object_done_offset(); - auto state = realm.heap().allocate(); - auto callback = NativeFunction::create( - *vm.current_realm(), [objects_with_properties = move(objects_with_properties), result_object, value_offset, done_offset, state](VM& vm) mutable -> ThrowCompletionOr { + *vm.current_realm(), [items = move(properties), result_object, value_offset, done_offset](VM& vm) mutable -> ThrowCompletionOr { + auto& iterated_object = vm.this_value().as_object(); while (true) { - if (objects_with_properties.is_empty()) { + if (items.is_empty()) { result_object->put_direct(done_offset, JS::Value(true)); return result_object; } - auto& object = objects_with_properties.last(); + auto key = move(items.take_first().key); - if (object.property_names.is_empty() - || !object.object) { - objects_with_properties.take_last(); - continue; - } - - auto key = object.property_names.take_first(); - - // NOTE: If there is a non-enumerable property higher up the prototype chain with the same key, - // we mustn't include this property even if it's enumerable (invariant no. 5 and 6) - if (state->non_enumerable_property_keys.contains(key)) + // If the property is deleted, don't include it (invariant no. 2) + if (!TRY(iterated_object.has_property(key))) continue; - auto descriptor = TRY(object.object->internal_get_own_property(key)); - if (!descriptor.has_value()) - continue; - - if (!*descriptor->enumerable) { - state->non_enumerable_property_keys.set(move(key)); - continue; - } + result_object->put_direct(done_offset, JS::Value(false)); if (key.is_number()) result_object->put_direct(value_offset, PrimitiveString::create(vm, String::number(key.as_number()))); @@ -1800,7 +1800,6 @@ inline ThrowCompletionOr get_object_property_iterator(VM& vm, Value val else VERIFY_NOT_REACHED(); // We should not have non-string/number keys. - result_object->put_direct(done_offset, JS::Value(false)); return result_object; } }, diff --git a/Libraries/LibJS/Tests/builtins/Proxy/for-in-iteration-traps.js b/Libraries/LibJS/Tests/builtins/Proxy/for-in-iteration-traps.js deleted file mode 100644 index 81d57824d09..00000000000 --- a/Libraries/LibJS/Tests/builtins/Proxy/for-in-iteration-traps.js +++ /dev/null @@ -1,40 +0,0 @@ -test("for..in iteration Proxy traps", () => { - let traps = []; - let array = [1, 2, 3]; - let from = new Proxy(array, { - getPrototypeOf: function (t) { - traps.push("getPrototypeOf"); - return Reflect.getPrototypeOf(t); - }, - ownKeys: function (t) { - traps.push("ownKeys"); - return Reflect.ownKeys(t); - }, - has: function (t, p) { - traps.push("has"); - return Reflect.has(t, p); - }, - getOwnPropertyDescriptor: function (t, p) { - traps.push("getOwnPropertyDescriptor"); - return Reflect.getOwnPropertyDescriptor(t, p); - }, - }); - const to = []; - for (const prop in from) { - to.push(prop); - from.pop(); - } - - expect(to).toEqual(["0", "1"]); - - expect(traps).toEqual([ - "ownKeys", - "getPrototypeOf", - "getOwnPropertyDescriptor", - "getOwnPropertyDescriptor", - "getOwnPropertyDescriptor", - "getOwnPropertyDescriptor", - "getOwnPropertyDescriptor", - "getOwnPropertyDescriptor", - ]); -}); From c49dd2036bad3248a31b319df762d84f9235b7f2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 21 Mar 2025 20:08:47 +0100 Subject: [PATCH 141/141] LibWeb: Mark height as definite before doing inner layout of abspos ...boxes with non-auto height. We know for sure that by the time we layout abspos boxes, their containing block has definite height, so it's possible to resolve non-auto heights and mark it as definite before doing inner layout. Big step towards having reasonable performance on https://demo.immich.app/photos because now we avoid a bunch of work initiated by mistakenly invoked intersection observer callbacks. Co-Authored-By: Andreas Kling --- Libraries/LibWeb/Layout/FormattingContext.cpp | 11 +++++++---- ...abspos-box-with-percentage-height-child.txt | 13 +++++++++++++ ...bspos-box-with-percentage-height-child.html | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.txt create mode 100644 Tests/LibWeb/Layout/input/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.html diff --git a/Libraries/LibWeb/Layout/FormattingContext.cpp b/Libraries/LibWeb/Layout/FormattingContext.cpp index 6acb33d1c6f..9e7df9fdbf7 100644 --- a/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -1170,9 +1170,9 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el box_state.set_content_height(used_height.to_px(box)); // do not set calculated insets or margins on the first pass, there will be a second pass - if (before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) + if (box.computed_values().height().is_auto() && before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) return; - + box_state.set_has_definite_height(true); box_state.inset_top = top.to_px(box, height_of_containing_block); box_state.inset_bottom = bottom.to_px(box, height_of_containing_block); box_state.margin_top = margin_top.to_px(box, width_of_containing_block); @@ -1255,7 +1255,9 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava auto independent_formatting_context = layout_inside(box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(available_space)); - compute_height_for_absolutely_positioned_element(box, available_space, BeforeOrAfterInsideLayout::After); + if (box.computed_values().height().is_auto()) { + compute_height_for_absolutely_positioned_element(box, available_space, BeforeOrAfterInsideLayout::After); + } CSSPixelPoint used_offset; @@ -1350,8 +1352,9 @@ void FormattingContext::compute_height_for_absolutely_positioned_replaced_elemen box_state.set_content_height(height); // do not set calculated insets or margins on the first pass, there will be a second pass - if (before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) + if (box.computed_values().height().is_auto() && before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) return; + box_state.set_has_definite_height(true); box_state.inset_top = to_px(top); box_state.inset_bottom = to_px(bottom); box_state.margin_top = to_px(margin_top); diff --git a/Tests/LibWeb/Layout/expected/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.txt b/Tests/LibWeb/Layout/expected/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.txt new file mode 100644 index 00000000000..f3839de3425 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.txt @@ -0,0 +1,13 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x16 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x0 positioned children: not-inline + BlockContainer
at (8,8) content-size 120x0 positioned [BFC] children: not-inline + BlockContainer at (18,18) content-size 100x0 children: not-inline + BlockContainer at (18,18) content-size 100x30 children: not-inline + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x16] + PaintableWithLines (BlockContainer) [8,8 784x0] + PaintableWithLines (BlockContainer
) [8,8 120x0] overflow: [8,8 120x40] + PaintableWithLines (BlockContainer
#asset-grid) [8,8 120x20] overflow: [18,18 100x30] + PaintableWithLines (BlockContainer
#virtual-timeline) [18,18 100x30] diff --git a/Tests/LibWeb/Layout/input/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.html b/Tests/LibWeb/Layout/input/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.html new file mode 100644 index 00000000000..2205c6d8ba9 --- /dev/null +++ b/Tests/LibWeb/Layout/input/block-and-inline/percentage-height-abspos-box-with-percentage-height-child.html @@ -0,0 +1,18 @@ +
\ No newline at end of file