diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 5f464ae5725..06672b16da3 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -3600,7 +3600,7 @@ Optional Element::auto_directionality() const // 1. If element's value contains a character of bidirectional character type AL or R, // and there is no character of bidirectional character type L anywhere before it in the element's value, then return 'rtl'. - for (auto code_point : Utf8View(value)) { + for (auto code_point : value) { auto bidi_class = Unicode::bidirectional_class(code_point); if (bidi_class == Unicode::BidiClass::LeftToRight) break; diff --git a/Libraries/LibWeb/HTML/Dates.cpp b/Libraries/LibWeb/HTML/Dates.cpp index 684088a4591..308f2123347 100644 --- a/Libraries/LibWeb/HTML/Dates.cpp +++ b/Libraries/LibWeb/HTML/Dates.cpp @@ -33,30 +33,33 @@ u32 week_number_of_the_last_day(u64 year) } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-week-string -bool is_valid_week_string(StringView value) +bool is_valid_week_string(Utf16View const& value) { // A string is a valid week string representing a week-year year and week week if it consists of the following components in the given order: // 1. Four or more ASCII digits, representing year, where year > 0 // 2. A U+002D HYPHEN-MINUS character (-) // 3. A U+0057 LATIN CAPITAL LETTER W character (W) - // 4. Two ASCII digits, representing the week week, in the range 1 ≤ week ≤ maxweek, where maxweek is the week number of the last day of week-year year + // 4. Two ASCII digits, representing the week week, in the range 1 ≤ week ≤ maxweek, where maxweek is the week number + // of the last day of week-year year auto parts = value.split_view('-', SplitBehavior::KeepEmpty); if (parts.size() != 2) return false; - if (parts[0].length() < 4) + + if (parts[0].length_in_code_units() < 4) return false; + if (parts[1].length_in_code_units() != 3) + return false; + for (auto digit : parts[0]) if (!is_ascii_digit(digit)) return false; - if (parts[1].length() != 3) - return false; - if (!parts[1].starts_with('W')) + if (!parts[1].starts_with("W"sv)) return false; - if (!is_ascii_digit(parts[1][1])) + if (!is_ascii_digit(parts[1].code_unit_at(1))) return false; - if (!is_ascii_digit(parts[1][2])) + if (!is_ascii_digit(parts[1].code_unit_at(2))) return false; u64 year = 0; @@ -64,13 +67,13 @@ bool is_valid_week_string(StringView value) year *= 10; year += parse_ascii_digit(d); } - auto week = (parse_ascii_digit(parts[1][1]) * 10) + parse_ascii_digit(parts[1][2]); + auto week = (parse_ascii_digit(parts[1].code_unit_at(1)) * 10) + parse_ascii_digit(parts[1].code_unit_at(2)); return week >= 1 && week <= week_number_of_the_last_day(year); } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string -bool is_valid_month_string(StringView value) +bool is_valid_month_string(Utf16View const& value) { // A string is a valid month string representing a year year and month month if it consists of the following components in the given order: @@ -82,40 +85,42 @@ bool is_valid_month_string(StringView value) if (parts.size() != 2) return false; - if (parts[0].length() < 4) + if (parts[0].length_in_code_units() < 4) return false; + if (parts[1].length_in_code_units() != 2) + return false; + for (auto digit : parts[0]) if (!is_ascii_digit(digit)) return false; - if (parts[1].length() != 2) + if (!is_ascii_digit(parts[1].code_unit_at(0))) + return false; + if (!is_ascii_digit(parts[1].code_unit_at(1))) return false; - if (!is_ascii_digit(parts[1][0])) - return false; - if (!is_ascii_digit(parts[1][1])) - return false; - - auto month = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]); + auto month = (parse_ascii_digit(parts[1].code_unit_at(0)) * 10) + parse_ascii_digit(parts[1].code_unit_at(1)); return month >= 1 && month <= 12; } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string -bool is_valid_date_string(StringView value) +bool is_valid_date_string(Utf16View const& value) { // A string is a valid date string representing a year year, month month, and day day if it consists of the following components in the given order: // 1. A valid month string, representing year and month // 2. A U+002D HYPHEN-MINUS character (-) - // 3. Two ASCII digits, representing day, in the range 1 ≤ day ≤ maxday where maxday is the number of days in the month month and year year + // 3. Two ASCII digits, representing day, in the range 1 ≤ day ≤ maxday where maxday is the number of days in the + // month month and year year auto parts = value.split_view('-', SplitBehavior::KeepEmpty); if (parts.size() != 3) return false; - if (!is_valid_month_string(ByteString::formatted("{}-{}", parts[0], parts[1]))) + auto month_string = value.substring_view(0, parts[0].length_in_code_units() + 1 + parts[1].length_in_code_units()); + if (!is_valid_month_string(month_string)) return false; - if (parts[2].length() != 2) + if (parts[2].length_in_code_units() != 2) return false; i64 year = 0; @@ -123,18 +128,20 @@ bool is_valid_date_string(StringView value) year *= 10; year += parse_ascii_digit(d); } - auto month = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]); - i64 day = (parse_ascii_digit(parts[2][0]) * 10) + parse_ascii_digit(parts[2][1]); + + auto month = (parse_ascii_digit(parts[1].code_unit_at(0)) * 10) + parse_ascii_digit(parts[1].code_unit_at(1)); + i64 day = (parse_ascii_digit(parts[2].code_unit_at(0)) * 10) + parse_ascii_digit(parts[2].code_unit_at(1)); return day >= 1 && day <= AK::days_in_month(year, month); } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string -bool is_valid_local_date_and_time_string(StringView value) +bool is_valid_local_date_and_time_string(Utf16View const& value) { auto parts_split_by_T = value.split_view('T', SplitBehavior::KeepEmpty); if (parts_split_by_T.size() == 2) return is_valid_date_string(parts_split_by_T[0]) && is_valid_time_string(parts_split_by_T[1]); + auto parts_split_by_space = value.split_view(' ', SplitBehavior::KeepEmpty); if (parts_split_by_space.size() == 2) return is_valid_date_string(parts_split_by_space[0]) && is_valid_time_string(parts_split_by_space[1]); @@ -143,11 +150,11 @@ bool is_valid_local_date_and_time_string(StringView value) } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string -String normalize_local_date_and_time_string(String const& value) +Utf16String normalize_local_date_and_time_string(Utf16String const& value) { if (auto spaces = value.count(" "sv); spaces > 0) { VERIFY(spaces == 1); - return MUST(value.replace(" "sv, "T"sv, ReplaceMode::FirstOnly)); + return value.replace(" "sv, "T"sv, ReplaceMode::FirstOnly); } VERIFY(value.count("T"sv) == 1); @@ -155,7 +162,7 @@ String normalize_local_date_and_time_string(String const& value) } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-time-string -bool is_valid_time_string(StringView value) +bool is_valid_time_string(Utf16View const& value) { // A string is a valid time string representing an hour hour, a minute minute, and a second second if it consists of the following components in the given order: @@ -163,49 +170,59 @@ bool is_valid_time_string(StringView value) // 2. A U+003A COLON character (:) // 3. Two ASCII digits, representing minute, in the range 0 ≤ minute ≤ 59 // 4. If second is nonzero, or optionally if second is zero: - // 1. A U+003A COLON character (:) - // 2. Two ASCII digits, representing the integer part of second, in the range 0 ≤ s ≤ 59 - // 3. If second is not an integer, or optionally if second is an integer: - // 1. A U+002E FULL STOP character (.) - // 2. One, two, or three ASCII digits, representing the fractional part of second + // 1. A U+003A COLON character (:) + // 2. Two ASCII digits, representing the integer part of second, in the range 0 ≤ s ≤ 59 + // 3. If second is not an integer, or optionally if second is an integer: + // 1. A U+002E FULL STOP character (.) + // 2. One, two, or three ASCII digits, representing the fractional part of second auto parts = value.split_view(':', SplitBehavior::KeepEmpty); if (parts.size() != 2 && parts.size() != 3) return false; - if (parts[0].length() != 2) + + if (parts[0].length_in_code_units() != 2) return false; - if (!(is_ascii_digit(parts[0][0]) && is_ascii_digit(parts[0][1]))) + if (parts[1].length_in_code_units() != 2) return false; - auto hour = (parse_ascii_digit(parts[0][0]) * 10) + parse_ascii_digit(parts[0][1]); + + if (!is_ascii_digit(parts[0].code_unit_at(0)) || !is_ascii_digit(parts[0].code_unit_at(1))) + return false; + + auto hour = (parse_ascii_digit(parts[0].code_unit_at(0)) * 10) + parse_ascii_digit(parts[0].code_unit_at(1)); if (hour > 23) return false; - if (parts[1].length() != 2) + + if (!is_ascii_digit(parts[1].code_unit_at(0)) || !is_ascii_digit(parts[1].code_unit_at(1))) return false; - if (!(is_ascii_digit(parts[1][0]) && is_ascii_digit(parts[1][1]))) - return false; - auto minute = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]); + + auto minute = (parse_ascii_digit(parts[1].code_unit_at(0)) * 10) + parse_ascii_digit(parts[1].code_unit_at(1)); if (minute > 59) return false; - if (parts.size() == 2) - return true; - if (parts[2].length() < 2) - return false; - if (!(is_ascii_digit(parts[2][0]) && is_ascii_digit(parts[2][1]))) - return false; - auto second = (parse_ascii_digit(parts[2][0]) * 10) + parse_ascii_digit(parts[2][1]); - if (second > 59) - return false; - if (parts[2].length() == 2) - return true; - auto second_parts = parts[2].split_view('.', SplitBehavior::KeepEmpty); - if (second_parts.size() != 2) - return false; - if (second_parts[1].length() < 1 || second_parts[1].length() > 3) - return false; - for (auto digit : second_parts[1]) - if (!is_ascii_digit(digit)) + if (parts.size() == 3) { + if (parts[2].length_in_code_units() < 2) return false; + if (!is_ascii_digit(parts[2].code_unit_at(0)) || !is_ascii_digit(parts[2].code_unit_at(1))) + return false; + + auto second = (parse_ascii_digit(parts[2].code_unit_at(0)) * 10) + parse_ascii_digit(parts[2].code_unit_at(1)); + if (second > 59) + return false; + + if (parts[2].length_in_code_units() > 2) { + auto fractional = parts[2].split_view('.', SplitBehavior::KeepEmpty); + if (fractional.size() != 2) + return false; + + if (fractional[1].length_in_code_units() < 1 || fractional[1].length_in_code_units() > 3) + return false; + + for (auto digit : fractional[1]) + if (!is_ascii_digit(digit)) + return false; + } + } + return true; } diff --git a/Libraries/LibWeb/HTML/Dates.h b/Libraries/LibWeb/HTML/Dates.h index 05bbcebfca9..1fd6354e1ee 100644 --- a/Libraries/LibWeb/HTML/Dates.h +++ b/Libraries/LibWeb/HTML/Dates.h @@ -7,19 +7,19 @@ #pragma once #include -#include +#include #include #include namespace Web::HTML { u32 week_number_of_the_last_day(u64 year); -bool is_valid_week_string(StringView value); -bool is_valid_month_string(StringView value); -bool is_valid_date_string(StringView value); -bool is_valid_local_date_and_time_string(StringView value); -String normalize_local_date_and_time_string(String const& value); -bool is_valid_time_string(StringView value); +bool is_valid_week_string(Utf16View const& value); +bool is_valid_month_string(Utf16View const& value); +bool is_valid_date_string(Utf16View const& value); +bool is_valid_local_date_and_time_string(Utf16View const& value); +Utf16String normalize_local_date_and_time_string(Utf16String const& value); +bool is_valid_time_string(Utf16View const& value); WebIDL::ExceptionOr> parse_time_string(JS::Realm& realm, StringView value); struct YearAndMonth { diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index ffee6a1e534..88d0551f989 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -125,7 +125,7 @@ public: virtual bool suffering_from_bad_input() const { return false; } bool suffering_from_a_custom_error() const; - virtual String value() const { return String {}; } + virtual Utf16String value() const { return {}; } virtual Optional optional_value() const { VERIFY_NOT_REACHED(); } virtual HTMLElement& form_associated_element_to_html_element() = 0; diff --git a/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp b/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp index 17ce4f7ca3b..ef704a8e8e0 100644 --- a/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp +++ b/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp @@ -144,7 +144,7 @@ WebIDL::ExceptionOr>> construct_entry_list(J if (auto* select_element = dynamic_cast(control.ptr())) { for (auto const& option_element : select_element->list_of_options()) { if (option_element->selected() && !option_element->disabled()) { - entry_list.append(TRY(create_entry(realm, name.to_string(), option_element->value()))); + entry_list.append(TRY(create_entry(realm, name.to_string(), option_element->value().to_utf8_but_should_be_ported_to_utf16()))); } } } @@ -153,11 +153,11 @@ WebIDL::ExceptionOr>> construct_entry_list(J // 1. If the field element has a value attribute specified, then let value be the value of that attribute; otherwise, let value be the string "on". auto value = checkbox_or_radio_element->value(); if (value.is_empty()) - value = "on"_string; + value = "on"_utf16; // 2. Create an entry with name and value, and append it to entry list. auto checkbox_or_radio_element_name = checkbox_or_radio_element->name(); - entry_list.append(TRY(create_entry(realm, checkbox_or_radio_element_name->to_string(), value))); + entry_list.append(TRY(create_entry(realm, checkbox_or_radio_element_name->to_string(), value.to_utf8_but_should_be_ported_to_utf16()))); } // 8. Otherwise, if the field element is an input element whose type attribute is in the File Upload state, then: else if (auto* file_element = dynamic_cast(control.ptr()); file_element && file_element->type_state() == HTMLInputElement::TypeAttributeState::FileUpload) { @@ -186,7 +186,7 @@ WebIDL::ExceptionOr>> construct_entry_list(J } // 10. Otherwise, create an entry with name and the value of the field element, and append it to entry list. else { - entry_list.append(TRY(create_entry(realm, name.to_string(), control_as_form_associated_element->value()))); + entry_list.append(TRY(create_entry(realm, name.to_string(), control_as_form_associated_element->value().to_utf8_but_should_be_ported_to_utf16()))); } // 11. If the element has a dirname attribute, and that attribute's value is not the empty string, then: diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp index e5b896e13bb..4d979d84ef6 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp @@ -117,10 +117,10 @@ bool HTMLButtonElement::is_submit_button() const } // https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-value -String HTMLButtonElement::value() const +Utf16String HTMLButtonElement::value() const { // The element's value is the value of the element's value attribute, if there is one; otherwise the empty string. - return attribute(AttributeNames::value).value_or(String {}); + return Utf16String::from_utf8(attribute(AttributeNames::value).value_or(String {})); } // https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-optional-value diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.h b/Libraries/LibWeb/HTML/HTMLButtonElement.h index a857cdb7a2d..2dafbc81ad8 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.h +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.h @@ -75,7 +75,7 @@ public: // https://www.w3.org/TR/html-aria/#el-button virtual Optional default_role() const override { return ARIA::Role::button; } - virtual String value() const override; + virtual Utf16String value() const override; virtual Optional optional_value() const override; virtual bool has_activation_behavior() const override; diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.idl b/Libraries/LibWeb/HTML/HTMLButtonElement.idl index 3fa1ab1ae9f..371f8ddf599 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.idl +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.idl @@ -26,7 +26,7 @@ interface HTMLButtonElement : HTMLElement { [CEReactions, Reflect=formtarget] attribute DOMString formTarget; [CEReactions, Reflect] attribute DOMString name; [CEReactions, ImplementedAs=type_for_bindings, Enumerated=ButtonTypeState] attribute DOMString type; - [CEReactions, Reflect] attribute DOMString value; + [CEReactions, Reflect] attribute Utf16DOMString value; readonly attribute boolean willValidate; readonly attribute ValidityState validity; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index c56e6a5db8e..3000c0c17df 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -404,7 +404,7 @@ static void show_the_picker_if_applicable(HTMLInputElement& element) 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))); + element.document().browsing_context()->top_level_browsing_context()->page().did_request_color_picker(weak_element, Color::from_utf16_string(element.value()).value_or(Color(0, 0, 0))); } } } @@ -530,7 +530,7 @@ void HTMLInputElement::did_edit_text_node() { // An input element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the value. auto old_value = move(m_value); - m_value = value_sanitization_algorithm(m_text_node->data().to_utf8_but_should_be_ported_to_utf16()); + m_value = value_sanitization_algorithm(m_text_node->data()); m_dirty_value = true; m_has_uncommitted_changes = true; @@ -547,7 +547,7 @@ void HTMLInputElement::did_pick_color(Optional picked_color, ColorPickerU { if (type_state() == TypeAttributeState::Color && picked_color.has_value()) { // then when the user changes the element's value - m_value = value_sanitization_algorithm(picked_color.value().to_string_without_alpha()); + m_value = value_sanitization_algorithm(picked_color->to_utf16_string_without_alpha()); m_dirty_value = true; update_color_well_element(); @@ -630,7 +630,7 @@ void HTMLInputElement::did_select_files(Span selected_files, Multi }); } -String HTMLInputElement::value() const +Utf16String HTMLInputElement::value() const { switch (value_attribute_mode()) { // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value @@ -642,21 +642,23 @@ String HTMLInputElement::value() const case ValueAttributeMode::Default: // On getting, if the element has a value content attribute, return that attribute's value; otherwise, return // the empty string. - return get_attribute_value(AttributeNames::value); + return Utf16String::from_utf8(get_attribute_value(AttributeNames::value)); // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on case ValueAttributeMode::DefaultOn: // On getting, if the element has a value content attribute, return that attribute's value; otherwise, return // the string "on". - return get_attribute(AttributeNames::value).value_or("on"_string); + if (auto value = get_attribute(AttributeNames::value); value.has_value()) + return Utf16String::from_utf8(*value); + return "on"_utf16; // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename case ValueAttributeMode::Filename: // On getting, return the string "C:\fakepath\" followed by the name of the first file in the list of selected // files, if any, or the empty string if the list is empty. if (m_selected_files && m_selected_files->item(0)) - return MUST(String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name())); - return String {}; + return Utf16String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name()); + return {}; } VERIFY_NOT_REACHED(); @@ -674,7 +676,7 @@ Optional HTMLInputElement::optional_value() const } } -WebIDL::ExceptionOr HTMLInputElement::set_value(String const& value) +WebIDL::ExceptionOr HTMLInputElement::set_value(Utf16String const& value) { auto& realm = this->realm(); @@ -700,7 +702,7 @@ WebIDL::ExceptionOr HTMLInputElement::set_value(String const& value) relevant_value_was_changed(); if (m_text_node) { - m_text_node->set_data(Utf16String::from_utf8(m_value)); + m_text_node->set_data(m_value); update_placeholder_visibility(); set_the_selection_range(m_text_node->length(), m_text_node->length()); @@ -796,30 +798,36 @@ void HTMLInputElement::update_placeholder_visibility() m_placeholder_element->set_inline_style(placeholder_style_when_hidden()); } +Utf16String HTMLInputElement::button_label() const +{ + auto label = get_attribute(HTML::AttributeNames::value).map([](auto const& label) { return Utf16String::from_utf8(label); }); + + if (!label.has_value()) { + if (type_state() == TypeAttributeState::ResetButton) { + // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset) + // If the element has a value attribute, the button's label must be the value of that attribute; + // otherwise, it must be an implementation-defined string that means "Reset" or some such. + label = "Reset"_utf16; + } else if (type_state() == TypeAttributeState::SubmitButton) { + // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit) + // If the element has a value attribute, the button's label must be the value of that attribute; + // otherwise, it must be an implementation-defined string that means "Submit" or some such. + label = "Submit"_utf16; + } else { + // https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button) + // If the element has a value attribute, the button's label must be the value of that attribute; + // otherwise, it must be the empty string. + label = value(); + } + } + + return label.release_value(); +} + void HTMLInputElement::update_button_input_shadow_tree() { if (m_text_node) { - Optional label = get_attribute(HTML::AttributeNames::value); - if (!label.has_value()) { - if (type_state() == TypeAttributeState::ResetButton) { - // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset) - // If the element has a value attribute, the button's label must be the value of that attribute; - // otherwise, it must be an implementation-defined string that means "Reset" or some such. - label = "Reset"_string; - } else if (type_state() == TypeAttributeState::SubmitButton) { - // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit) - // If the element has a value attribute, the button's label must be the value of that attribute; - // otherwise, it must be an implementation-defined string that means "Submit" or some such. - label = "Submit"_string; - } else { - // https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button) - // If the element has a value attribute, the button's label must be the value of that attribute; - // otherwise, it must be the empty string. - label = value(); - } - } - - m_text_node->set_data(Utf16String::from_utf8(label.value())); + m_text_node->set_data(button_label()); update_placeholder_visibility(); } } @@ -827,7 +835,7 @@ void HTMLInputElement::update_button_input_shadow_tree() void HTMLInputElement::update_text_input_shadow_tree() { if (m_text_node) { - m_text_node->set_data(Utf16String::from_utf8(m_value)); + m_text_node->set_data(m_value); update_placeholder_visibility(); } } @@ -999,26 +1007,8 @@ void HTMLInputElement::create_button_input_shadow_tree() set_shadow_root(shadow_root); auto text_container = MUST(DOM::create_element(document(), HTML::TagNames::span, Namespace::HTML)); MUST(text_container->set_attribute(HTML::AttributeNames::style, "display: inline-block; pointer-events: none;"_string)); - Optional label = get_attribute(HTML::AttributeNames::value); - if (!label.has_value()) { - if (type_state() == TypeAttributeState::ResetButton) { - // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset) - // If the element has a value attribute, the button's label must be the value of that attribute; - // otherwise, it must be an implementation-defined string that means "Reset" or some such. - label = "Reset"_string; - } else if (type_state() == TypeAttributeState::SubmitButton) { - // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit) - // If the element has a value attribute, the button's label must be the value of that attribute; - // otherwise, it must be an implementation-defined string that means "Submit" or some such. - label = "Submit"_string; - } else { - // https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button) - // If the element has a value attribute, the button's label must be the value of that attribute; - // otherwise, it must be the empty string. - label = value(); - } - } - m_text_node = realm().create(document(), Utf16String::from_utf8(label.value())); + + m_text_node = realm().create(document(), button_label()); MUST(text_container->append_child(*m_text_node)); MUST(shadow_root->append_child(*text_container)); } @@ -1074,7 +1064,7 @@ void HTMLInputElement::create_text_input_shadow_tree() } MUST(element->append_child(*m_inner_text_element)); - m_text_node = realm().create(document(), Utf16String::from_utf8(initial_value)); + m_text_node = realm().create(document(), move(initial_value)); handle_readonly_attribute(attribute(HTML::AttributeNames::readonly)); if (type_state() == TypeAttributeState::Password) m_text_node->set_is_password_input({}, true); @@ -1163,7 +1153,7 @@ void HTMLInputElement::create_color_input_shadow_tree() border: 1px solid ButtonBorder; box-sizing: border-box; )~~~"_string)); - MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, color)); + MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, color.to_utf8_but_should_be_ported_to_utf16())); MUST(border->append_child(*m_color_well_element)); MUST(shadow_root->append_child(border)); @@ -1175,7 +1165,7 @@ void HTMLInputElement::update_color_well_element() if (!m_color_well_element) return; - MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value)); + MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value.to_utf8_but_should_be_ported_to_utf16())); } void HTMLInputElement::create_file_input_shadow_tree() @@ -1407,11 +1397,11 @@ void HTMLInputElement::form_associated_element_attribute_changed(FlyString const } else if (name == HTML::AttributeNames::value) { if (!m_dirty_value) { auto old_value = move(m_value); - if (!value.has_value()) { - m_value = String {}; - } else { - m_value = value_sanitization_algorithm(*value); - } + + if (value.has_value()) + m_value = value_sanitization_algorithm(Utf16String::from_utf8(*value)); + else + m_value = {}; if (m_value != old_value) relevant_value_was_changed(); @@ -1458,7 +1448,7 @@ void HTMLInputElement::type_attribute_changed(TypeAttributeState old_state, Type // then set the value of the element to the value of the value content attribute, if there is one, or the empty string // otherwise, and then set the control's dirty value flag to false. else if (old_value_attribute_mode != ValueAttributeMode::Value && new_value_attribute_mode == ValueAttributeMode::Value) { - m_value = attribute(HTML::AttributeNames::value).value_or({}); + m_value = Utf16String::from_utf8(attribute(HTML::AttributeNames::value).value_or({})); m_dirty_value = false; } @@ -1466,7 +1456,7 @@ void HTMLInputElement::type_attribute_changed(TypeAttributeState old_state, Type // than the filename mode, and the new state of the element's type attribute puts the value IDL attribute in the filename mode, // then set the value of the element to the empty string. else if (old_value_attribute_mode != ValueAttributeMode::Filename && new_value_attribute_mode == ValueAttributeMode::Filename) { - m_value = String {}; + m_value = {}; } // 4. Update the element's rendering and behavior to the new state's. @@ -1618,129 +1608,147 @@ bool HTMLInputElement::can_have_text_editing_cursor() const } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-simple-colour -static bool is_valid_simple_color(StringView value) +static bool is_valid_simple_color(Utf16View const& value) { // if it is exactly seven characters long, - if (value.length() != 7) + if (value.length_in_code_units() != 7) return false; // and the first character is a U+0023 NUMBER SIGN character (#), - if (!value.starts_with('#')) + if (!value.starts_with("#"sv)) return false; // and the remaining six characters are all ASCII hex digits - for (size_t i = 1; i < value.length(); i++) - if (!is_ascii_hex_digit(value[i])) + for (size_t i = 1; i < value.length_in_code_units(); i++) + if (!is_ascii_hex_digit(value.code_unit_at(i))) return false; return true; } // https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm -String HTMLInputElement::value_sanitization_algorithm(String const& value) const +Utf16String HTMLInputElement::value_sanitization_algorithm(Utf16String const& value) const { - if (type_state() == HTMLInputElement::TypeAttributeState::Text || type_state() == HTMLInputElement::TypeAttributeState::Search || type_state() == HTMLInputElement::TypeAttributeState::Telephone || type_state() == HTMLInputElement::TypeAttributeState::Password) { + auto strip_newlines = [&]() { + if (!value.contains('\r') && !value.contains('\n')) + return value; + + StringBuilder builder(StringBuilder::Mode::UTF16); + + for (size_t i = 0; i < value.length_in_code_units(); ++i) { + auto code_unit = value.code_unit_at(i); + if (code_unit != '\r' && code_unit != '\n') + builder.append_code_unit(code_unit); + } + + return builder.to_utf16_string(); + }; + + auto strip_newlines_and_trim = [&]() { + auto value_without_newlines = strip_newlines(); + return Utf16String::from_utf16_without_validation(value_without_newlines.utf16_view().trim(Infra::ASCII_WHITESPACE)); + }; + + // https://html.spec.whatwg.org/multipage/input.html#text-(type=text)-state-and-search-state-(type=search):value-sanitization-algorithm + // https://html.spec.whatwg.org/multipage/input.html#telephone-state-(type=tel):value-sanitization-algorithm + // https://html.spec.whatwg.org/multipage/input.html#password-state-(type=password):value-sanitization-algorithm + if (first_is_one_of(type_state(), HTMLInputElement::TypeAttributeState::Text, HTMLInputElement::TypeAttributeState::Search, HTMLInputElement::TypeAttributeState::Telephone, HTMLInputElement::TypeAttributeState::Password)) { // Strip newlines from the value. - if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) { - StringBuilder builder; - for (auto c : value.bytes_as_string_view()) { - if (c != '\r' && c != '\n') - builder.append(c); - } - return MUST(builder.to_string()); - } - } else if (type_state() == HTMLInputElement::TypeAttributeState::URL) { + return strip_newlines(); + } + + // https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url):value-sanitization-algorithm + if (type_state() == HTMLInputElement::TypeAttributeState::URL) { // Strip newlines from the value, then strip leading and trailing ASCII whitespace from the value. - if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) { - StringBuilder builder; - for (auto c : value.bytes_as_string_view()) { - if (c != '\r' && c != '\n') - builder.append(c); - } - return MUST(String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE))); - } - return MUST(value.trim(Infra::ASCII_WHITESPACE)); - } else if (type_state() == HTMLInputElement::TypeAttributeState::Email) { + return strip_newlines_and_trim(); + } + + // https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email):value-sanitization-algorithm + // https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email):value-sanitization-algorithm-2 + if (type_state() == HTMLInputElement::TypeAttributeState::Email) { if (!has_attribute(AttributeNames::multiple)) { - // https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email):value-sanitization-algorithm // Strip newlines from the value, then strip leading and trailing ASCII whitespace from the value. - if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) { - StringBuilder builder; - for (auto c : value.bytes_as_string_view()) { - if (c != '\r' && c != '\n') - builder.append(c); - } - return MUST(String::from_utf8(builder.string_view().trim_whitespace())); - } - return MUST(value.trim_ascii_whitespace()); + return strip_newlines_and_trim(); } - // https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email):value-sanitization-algorithm-2 - // 1. Split on commas the element's value, strip leading and trailing ASCII whitespace from each resulting token, if any, - // and let the element's values be the (possibly empty) resulting list of (possibly empty) tokens, maintaining the original order. - Vector values {}; - for (auto const& token : MUST(value.split(',', SplitBehavior::KeepEmpty))) { - values.append(MUST(token.trim_ascii_whitespace())); - } + // 1. Split on commas the element's value, strip leading and trailing ASCII whitespace from each resulting token, + // if any, and let the element's values be the (possibly empty) resulting list of (possibly empty) tokens, + // maintaining the original order. + auto values = value.split_view(',', SplitBehavior::KeepEmpty); - // 2. Set the element's value to the result of concatenating the element's values, separating each value - // from the next by a single U+002C COMMA character (,), maintaining the list's order. - StringBuilder builder; - builder.join(',', values); - return MUST(builder.to_string()); + for (auto& value : values) + value = value.trim(Infra::ASCII_WHITESPACE); - } else if (type_state() == HTMLInputElement::TypeAttributeState::Number) { - // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm - // If the value of the element is not a valid floating-point number, then set it - // to the empty string instead. + // 2. Set the element's value to the result of concatenating the element's values, separating each value from + // the next by a single U+002C COMMA character (,), maintaining the list's order. + return Utf16String::join(',', values); + } + + // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm + if (type_state() == HTMLInputElement::TypeAttributeState::Number) { + // If the value of the element is not a valid floating-point number, then set it to the empty string instead. if (!is_valid_floating_point_number(value)) - return String {}; - auto maybe_value = parse_floating_point_number(value); - // AD-HOC: The spec doesn’t require these checks — but other engines do them, and - // there’s a WPT case which tests that the value is less than Number.MAX_VALUE. - if (!maybe_value.has_value() || !isfinite(maybe_value.value())) - return String {}; - } else if (type_state() == HTMLInputElement::TypeAttributeState::Date) { - // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm + return {}; + + // AD-HOC: The spec doesn't require these checks - but other engines do them, and there's a WPT case which tests + // that the value is less than Number.MAX_VALUE. + if (auto maybe_value = parse_floating_point_number(value); !maybe_value.has_value() || !isfinite(maybe_value.value())) + return {}; + } + // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::Date) { + // If the value of the element is not a valid date string, then set it to the empty string instead. if (!is_valid_date_string(value)) - return String {}; - } else if (type_state() == HTMLInputElement::TypeAttributeState::Month) { - // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm + return {}; + } + // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::Month) { + // If the value of the element is not a valid month string, then set it to the empty string instead. if (!is_valid_month_string(value)) - return String {}; - } else if (type_state() == HTMLInputElement::TypeAttributeState::Week) { - // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm + return {}; + } + // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::Week) { if (!is_valid_week_string(value)) - return String {}; - } else if (type_state() == HTMLInputElement::TypeAttributeState::Time) { - // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm + return {}; + } + // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::Time) { + // If the value of the element is not a valid week string, then set it to the empty string instead. if (!is_valid_time_string(value)) - return String {}; - } else if (type_state() == HTMLInputElement::TypeAttributeState::LocalDateAndTime) { - // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm + return {}; + } + // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::LocalDateAndTime) { + // If the value of the element is a valid local date and time string, then set it to a valid normalized local + // date and time string representing the same date and time; otherwise, set it to the empty string instead. if (is_valid_local_date_and_time_string(value)) return normalize_local_date_and_time_string(value); - return String {}; - } else if (type_state() == HTMLInputElement::TypeAttributeState::Range) { - // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm - // If the value of the element is not a valid floating-point number, then set it to the best representation, as a floating-point number, of the default value. - auto maybe_value = parse_floating_point_number(value); - if (!is_valid_floating_point_number(value) || - // AD-HOC: The spec doesn’t require these checks — but other engines do them. + return {}; + } + // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::Range) { + // If the value of the element is not a valid floating-point number, then set it to the best representation, as + // a floating-point number, of the default value. + if (auto maybe_value = parse_floating_point_number(value); !is_valid_floating_point_number(value) || + // AD-HOC: The spec doesn't require these checks - but other engines do them. !maybe_value.has_value() || !isfinite(maybe_value.value())) { - // The default value is the minimum plus half the difference between the minimum and the maximum, unless the maximum is less than the minimum, in which case the default value is the minimum. + // The default value is the minimum plus half the difference between the minimum and the maximum, unless the + // maximum is less than the minimum, in which case the default value is the minimum. auto minimum = *min(); auto maximum = *max(); if (maximum < minimum) - return JS::number_to_string(minimum); - return JS::number_to_string(minimum + (maximum - minimum) / 2); + return JS::number_to_utf16_string(minimum); + return JS::number_to_utf16_string(minimum + ((maximum - minimum) / 2.0)); } - } else if (type_state() == HTMLInputElement::TypeAttributeState::Color) { - // https://html.spec.whatwg.org/multipage/input.html#color-state-(type=color):value-sanitization-algorithm - // If the value of the element is a valid simple color, then set it to the value of the element converted to ASCII lowercase; + } + // https://html.spec.whatwg.org/multipage/input.html#color-state-(type=color):value-sanitization-algorithm + else if (type_state() == HTMLInputElement::TypeAttributeState::Color) { + // If the value of the element is a valid simple color, then set it to the value of the element converted to + // ASCII lowercase; otherwise, set it to the string "#000000". if (is_valid_simple_color(value)) return value.to_ascii_lowercase(); - // otherwise, set it to the string "#000000". - return "#000000"_string; + return "#000000"_utf16; } + return value; } @@ -1754,7 +1762,7 @@ void HTMLInputElement::reset_algorithm() // set the value of the element to the value of the value content attribute, if there is one, or the empty string otherwise, auto old_value = move(m_value); - m_value = get_attribute_value(AttributeNames::value); + m_value = Utf16String::from_utf8(get_attribute_value(AttributeNames::value)); // set the checkedness of the element to true if the element has a checked content attribute and false if it does not, m_checked = has_attribute(AttributeNames::checked); @@ -1770,7 +1778,7 @@ void HTMLInputElement::reset_algorithm() relevant_value_was_changed(); if (m_text_node) { - m_text_node->set_data(Utf16String::from_utf8(m_value)); + m_text_node->set_data(m_value); update_placeholder_visibility(); } @@ -1786,7 +1794,7 @@ void HTMLInputElement::clear_algorithm() // set the value of the element to an empty string, auto old_value = move(m_value); - m_value = String {}; + m_value = {}; // set the checkedness of the element to true if the element has a checked content attribute and false if it does not, m_checked = has_attribute(AttributeNames::checked); @@ -1806,7 +1814,7 @@ void HTMLInputElement::clear_algorithm() relevant_value_was_changed(); if (m_text_node) { - m_text_node->set_data(Utf16String::from_utf8(m_value)); + m_text_node->set_data(m_value); update_placeholder_visibility(); } @@ -2310,19 +2318,28 @@ Optional HTMLInputElement::convert_string_to_number(StringView input) co return {}; } +// https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number +Optional HTMLInputElement::convert_string_to_number(Utf16String const& input) const +{ + // FIXME: Implement a UTF-16 GenericLexer. + if (!input.has_ascii_storage()) + return {}; + return convert_string_to_number(input.ascii_view()); +} + // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):concept-input-value-number-string -static String convert_number_to_month_string(double input) +static Utf16String convert_number_to_month_string(double input) { // The algorithm to convert a number to a string, given a number input, is as follows: Return a valid month // string that represents the month that has input months between it and January 1970. auto months = JS::modulo(input, 12); auto year = 1970 + (input - months) / 12; - return MUST(String::formatted("{:04d}-{:02d}", static_cast(year), static_cast(months) + 1)); + return Utf16String::formatted("{:04d}-{:02d}", static_cast(year), static_cast(months) + 1); } // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):concept-input-value-string-number -static String convert_number_to_week_string(double input) +static Utf16String convert_number_to_week_string(double input) { // The algorithm to convert a number to a string, given a number input, is as follows: Return a valid week string that // that represents the week that, in UTC, is current input milliseconds after midnight UTC on the morning of 1970-01-01 @@ -2348,21 +2365,21 @@ static String convert_number_to_week_string(double input) week = weeks_in_year(year) + week; } - return MUST(String::formatted("{:04d}-W{:02d}", year, week)); + return Utf16String::formatted("{:04d}-W{:02d}", year, week); } // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-number-string -static String convert_number_to_date_string(double input) +static Utf16String convert_number_to_date_string(double input) { // The algorithm to convert a number to a string, given a number input, is as follows: Return a valid // date string that represents the date that, in UTC, is current input milliseconds after midnight UTC // on the morning of 1970-01-01 (the time represented by the value "1970-01-01T00:00:00.0Z"). auto date = AK::UnixDateTime::from_seconds_since_epoch(input / 1000.); - return MUST(date.to_string("%Y-%m-%d"sv, AK::UnixDateTime::LocalTime::No)); + return date.to_utf16_string("%Y-%m-%d"sv, AK::UnixDateTime::LocalTime::No); } // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-number-string -static String convert_number_to_time_string(double input) +static Utf16String convert_number_to_time_string(double input) { // The algorithm to convert a number to a string, given a number input, is as follows: Return a valid time // string that represents the time that is input milliseconds after midnight on a day with no time changes. @@ -2370,14 +2387,14 @@ static String convert_number_to_time_string(double input) auto milliseconds = JS::ms_from_time(input); if (seconds > 0) { if (milliseconds > 0) - return MUST(String::formatted("{:02d}:{:02d}:{:02d}.{:3d}", JS::hour_from_time(input), JS::min_from_time(input), seconds, milliseconds)); - return MUST(String::formatted("{:02d}:{:02d}:{:02d}", JS::hour_from_time(input), JS::min_from_time(input), seconds)); + return Utf16String::formatted("{:02d}:{:02d}:{:02d}.{:3d}", JS::hour_from_time(input), JS::min_from_time(input), seconds, milliseconds); + return Utf16String::formatted("{:02d}:{:02d}:{:02d}", JS::hour_from_time(input), JS::min_from_time(input), seconds); } - return MUST(String::formatted("{:02d}:{:02d}", JS::hour_from_time(input), JS::min_from_time(input))); + return Utf16String::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) +static Utf16String 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 @@ -2392,23 +2409,23 @@ static String convert_number_to_local_date_and_time_string(double 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 Utf16String::formatted("{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}.{:03d}", year, month, day, hour, minutes, seconds, milliseconds); + return Utf16String::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)); + return Utf16String::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 +Utf16String HTMLInputElement::convert_number_to_string(double input) const { // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-value-number-string if (type_state() == TypeAttributeState::Number) - return String::number(input); + return Utf16String::number(input); // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-value-number-string if (type_state() == TypeAttributeState::Range) - return String::number(input); + return Utf16String::number(input); if (type_state() == TypeAttributeState::Month) return convert_number_to_month_string(input); @@ -2459,8 +2476,17 @@ WebIDL::ExceptionOr> HTMLInputElement::convert_string_to_date( return nullptr; } +// https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-date +WebIDL::ExceptionOr> HTMLInputElement::convert_string_to_date(Utf16String const& input) const +{ + // FIXME: Implement a UTF-16 GenericLexer. + if (!input.has_ascii_storage()) + return nullptr; + return convert_string_to_date(input.ascii_view()); +} + // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-date-string -String HTMLInputElement::convert_date_to_string(GC::Ref input) const +Utf16String HTMLInputElement::convert_date_to_string(GC::Ref input) const { // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-date-string if (type_state() == TypeAttributeState::Date) { @@ -2665,12 +2691,12 @@ WebIDL::ExceptionOr HTMLInputElement::set_value_as_date(Optional(**value); if (!isfinite(date.date_value())) { - TRY(set_value(String {})); + TRY(set_value({})); return {}; } @@ -2704,7 +2730,7 @@ WebIDL::ExceptionOr HTMLInputElement::set_value_as_number(double value) // Otherwise, if the new value is a Not-a-Number (NaN) value, then set the value of the element to the empty string. if (value == NAN) { - TRY(set_value(String {})); + TRY(set_value({})); return {}; } @@ -3301,46 +3327,58 @@ bool HTMLInputElement::suffering_from_a_type_mismatch() const { auto input = value(); switch (type_state()) { + // https://html.spec.whatwg.org/multipage/input.html#url-state-(type%3Durl)%3Asuffering-from-a-type-mismatch case TypeAttributeState::URL: - // https://html.spec.whatwg.org/multipage/input.html#url-state-(type%3Durl)%3Asuffering-from-a-type-mismatch // While the value of the element is neither the empty string nor a valid absolute URL, the element is suffering from a type mismatch. // AD-HOC: https://github.com/whatwg/html/issues/11083 and https://github.com/web-platform-tests/wpt/pull/51011 // We intentionally don't check if the value is a "valid absolute URL", because that's not what other // engines actually do. So we instead just implement what matches the behavior in existing engines. - return !input.is_empty() && !URL::Parser::basic_parse(input).has_value(); - case TypeAttributeState::Email: - // https://html.spec.whatwg.org/multipage/input.html#email-state-(type%3Demail)%3Asuffering-from-a-type-mismatch + return !input.is_empty() && !URL::Parser::basic_parse(input.to_utf8_but_should_be_ported_to_utf16()).has_value(); + + // https://html.spec.whatwg.org/multipage/input.html#email-state-(type%3Demail)%3Asuffering-from-a-type-mismatch + case TypeAttributeState::Email: { // When the multiple attribute is not specified on the element: While the value of the element is neither the // empty string nor a single valid email address, the element is suffering from a type mismatch. if (!has_attribute(HTML::AttributeNames::multiple)) - return !input.is_empty() && !valid_email_address_regex.match(input).success; + return !input.is_empty() && !valid_email_address_regex.match(input.utf16_view()).success; + // When the multiple attribute is specified on the element: While the value of the element is not a valid email // address list, the element is suffering from a type mismatch. // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address-list // A valid email address list is a set of comma-separated tokens, where each token is itself a valid email // address. To obtain the list of tokens from a valid email address list, an implementation must split the // string on commas. - for (auto& address : MUST(input.split(','))) { - if (!valid_email_address_regex.match(address).success) - return true; - } - break; + bool valid = true; + + input.for_each_split_view(',', SplitBehavior::Nothing, [&](auto const& address) { + if (valid_email_address_regex.match(address).success) + return IterationDecision::Continue; + + valid = false; + return IterationDecision::Break; + }); + + return !valid; + } + default: break; } + return false; } // https://html.spec.whatwg.org/multipage/input.html#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch bool HTMLInputElement::suffering_from_a_pattern_mismatch() const { - // If the element's value is not the empty string, and either the element's multiple attribute is not specified or it does not apply to the input element given its - // type attribute's current state, and the element has a compiled pattern regular expression but that regular expression does not match the element's value, then the element is + // If the element's value is not the empty string, and either the element's multiple attribute is not specified or + // it does not apply to the input element given its type attribute's current state, and the element has a compiled + // pattern regular expression but that regular expression does not match the element's value, then the element is // suffering from a pattern mismatch. - // If the element's value is not the empty string, and the element's multiple attribute is specified and applies to the input element, - // and the element has a compiled pattern regular expression but that regular expression does not match each of the element's values, - // then the element is suffering from a pattern mismatch. + // If the element's value is not the empty string, and the element's multiple attribute is specified and applies to + // the input element, and the element has a compiled pattern regular expression but that regular expression does not + // match each of the element's values, then the element is suffering from a pattern mismatch. if (!pattern_applies()) return false; @@ -3355,13 +3393,20 @@ bool HTMLInputElement::suffering_from_a_pattern_mismatch() const if (has_attribute(HTML::AttributeNames::multiple) && multiple_applies()) { VERIFY(type_state() == HTMLInputElement::TypeAttributeState::Email); + bool valid = true; - return AK::any_of(MUST(value.split(',')), [®exp_object](auto const& value) { - return !regexp_object->match(value).success; + value.for_each_split_view(',', SplitBehavior::Nothing, [&](auto const& value) { + if (regexp_object->match(value).success) + return IterationDecision::Continue; + + valid = false; + return IterationDecision::Break; }); + + return !valid; } - return !regexp_object->match(value).success; + return !regexp_object->match(value.utf16_view()).success; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-underflow diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index e70ba394a2a..851e8c8ecdc 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -79,13 +79,13 @@ public: String default_value() const { return get_attribute_value(HTML::AttributeNames::value); } - virtual String value() const override; + virtual Utf16String value() const override; virtual Optional optional_value() const override; - WebIDL::ExceptionOr set_value(String const&); + WebIDL::ExceptionOr set_value(Utf16String const&); // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value - virtual Utf16String relevant_value() override { return Utf16String::from_utf8(value()); } - WebIDL::ExceptionOr set_relevant_value(Utf16String const& value) override { return set_value(value.to_utf8_but_should_be_ported_to_utf16()); } + virtual Utf16String relevant_value() override { return value(); } + WebIDL::ExceptionOr set_relevant_value(Utf16String const& value) override { return set_value(value); } virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; } @@ -290,10 +290,12 @@ private: Optional convert_time_string_to_number(StringView input) const; Optional convert_string_to_number(StringView input) const; - String convert_number_to_string(double input) const; + Optional convert_string_to_number(Utf16String const& input) const; + Utf16String convert_number_to_string(double input) const; WebIDL::ExceptionOr> convert_string_to_date(StringView input) const; - String convert_date_to_string(GC::Ref input) const; + WebIDL::ExceptionOr> convert_string_to_date(Utf16String const& input) const; + Utf16String convert_date_to_string(GC::Ref input) const; Optional min() const; Optional max() const; @@ -304,6 +306,9 @@ private: WebIDL::ExceptionOr step_up_or_down(bool is_down, WebIDL::Long n); static TypeAttributeState parse_type_attribute(StringView); + + Utf16String button_label() const; + void create_shadow_tree_if_needed(); void update_shadow_tree(); void create_button_input_shadow_tree(); @@ -321,7 +326,7 @@ private: void user_interaction_did_change_input_value(); // https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm - String value_sanitization_algorithm(String const&) const; + Utf16String value_sanitization_algorithm(Utf16String const&) const; enum class ValueAttributeMode { Value, @@ -386,7 +391,7 @@ private: GC::Ptr m_selected_files; TypeAttributeState m_type { TypeAttributeState::Text }; - String m_value; + Utf16String m_value; String m_last_src_value; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Libraries/LibWeb/HTML/HTMLInputElement.idl index 58e321433e3..6e4e9e040dc 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.idl +++ b/Libraries/LibWeb/HTML/HTMLInputElement.idl @@ -44,7 +44,7 @@ interface HTMLInputElement : HTMLElement { [CEReactions, Reflect] attribute boolean switch; [CEReactions] attribute DOMString type; [CEReactions, Reflect=value] attribute DOMString defaultValue; - [CEReactions, LegacyNullToEmptyString] attribute DOMString value; + [CEReactions, LegacyNullToEmptyString] attribute Utf16DOMString value; attribute object? valueAsDate; attribute unrestricted double valueAsNumber; [CEReactions] attribute unsigned long width; diff --git a/Libraries/LibWeb/HTML/HTMLOptionElement.cpp b/Libraries/LibWeb/HTML/HTMLOptionElement.cpp index cae528236d4..b0613401c65 100644 --- a/Libraries/LibWeb/HTML/HTMLOptionElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLOptionElement.cpp @@ -89,15 +89,15 @@ void HTMLOptionElement::set_selected_internal(bool selected) } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-value -String HTMLOptionElement::value() const +Utf16String HTMLOptionElement::value() const { // The value of an option element is the value of the value content attribute, if there is one. // ...or, if there is not, the value of the element's text IDL attribute. - return attribute(HTML::AttributeNames::value).value_or(text()); + return Utf16String::from_utf8(attribute(HTML::AttributeNames::value).value_or(text())); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-value -WebIDL::ExceptionOr HTMLOptionElement::set_value(String const& value) +WebIDL::ExceptionOr HTMLOptionElement::set_value(Utf16String const& value) { return set_attribute(HTML::AttributeNames::value, value); } diff --git a/Libraries/LibWeb/HTML/HTMLOptionElement.h b/Libraries/LibWeb/HTML/HTMLOptionElement.h index 0fd4f76e6bb..5465999d3ae 100644 --- a/Libraries/LibWeb/HTML/HTMLOptionElement.h +++ b/Libraries/LibWeb/HTML/HTMLOptionElement.h @@ -23,8 +23,8 @@ public: void set_selected_internal(bool); [[nodiscard]] u64 selectedness_update_index() const { return m_selectedness_update_index; } - String value() const; - WebIDL::ExceptionOr set_value(String const&); + Utf16String value() const; + WebIDL::ExceptionOr set_value(Utf16String const&); String text() const; void set_text(String const&); diff --git a/Libraries/LibWeb/HTML/HTMLOptionElement.idl b/Libraries/LibWeb/HTML/HTMLOptionElement.idl index de6ee059da9..39e09b4b60d 100644 --- a/Libraries/LibWeb/HTML/HTMLOptionElement.idl +++ b/Libraries/LibWeb/HTML/HTMLOptionElement.idl @@ -10,7 +10,7 @@ interface HTMLOptionElement : HTMLElement { [CEReactions] attribute DOMString label; [CEReactions, Reflect=selected] attribute boolean defaultSelected; attribute boolean selected; - [CEReactions] attribute DOMString value; + [CEReactions] attribute Utf16DOMString value; [CEReactions] attribute DOMString text; readonly attribute long index; diff --git a/Libraries/LibWeb/HTML/HTMLOutputElement.cpp b/Libraries/LibWeb/HTML/HTMLOutputElement.cpp index 55139ee2828..3e73a00038d 100644 --- a/Libraries/LibWeb/HTML/HTMLOutputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLOutputElement.cpp @@ -74,20 +74,20 @@ void HTMLOutputElement::set_default_value(String const& default_value) } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-output-value -String HTMLOutputElement::value() const +Utf16String HTMLOutputElement::value() const { // The value getter steps are to return this's descendant text content. - return descendant_text_content(); + return Utf16String::from_utf8(descendant_text_content()); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-output-value -void HTMLOutputElement::set_value(String const& value) +void HTMLOutputElement::set_value(Utf16String const& value) { // 1. Set this's default value override to its default value. m_default_value_override = default_value(); // 2. String replace all with the given value within this. - string_replace_all(value); + string_replace_all(value.to_utf8_but_should_be_ported_to_utf16()); } // https://html.spec.whatwg.org/multipage/form-elements.html#the-output-element:concept-form-reset-control diff --git a/Libraries/LibWeb/HTML/HTMLOutputElement.h b/Libraries/LibWeb/HTML/HTMLOutputElement.h index 4bf5c4a47c0..aef1b22e1b0 100644 --- a/Libraries/LibWeb/HTML/HTMLOutputElement.h +++ b/Libraries/LibWeb/HTML/HTMLOutputElement.h @@ -34,8 +34,8 @@ public: String default_value() const; void set_default_value(String const&); - String value() const override; - void set_value(String const&); + Utf16String value() const override; + void set_value(Utf16String const&); // ^FormAssociatedElement // https://html.spec.whatwg.org/multipage/forms.html#category-listed diff --git a/Libraries/LibWeb/HTML/HTMLOutputElement.idl b/Libraries/LibWeb/HTML/HTMLOutputElement.idl index f96e4480189..a91566e2220 100644 --- a/Libraries/LibWeb/HTML/HTMLOutputElement.idl +++ b/Libraries/LibWeb/HTML/HTMLOutputElement.idl @@ -13,7 +13,7 @@ interface HTMLOutputElement : HTMLElement { readonly attribute DOMString type; [CEReactions] attribute DOMString defaultValue; - [CEReactions] attribute DOMString value; + [CEReactions] attribute Utf16DOMString value; readonly attribute boolean willValidate; readonly attribute ValidityState validity; diff --git a/Libraries/LibWeb/HTML/HTMLSelectElement.cpp b/Libraries/LibWeb/HTML/HTMLSelectElement.cpp index d172924ffaf..7e6b60e43c6 100644 --- a/Libraries/LibWeb/HTML/HTMLSelectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLSelectElement.cpp @@ -365,16 +365,16 @@ Optional HTMLSelectElement::default_role() const return ARIA::Role::combobox; } -String HTMLSelectElement::value() const +Utf16String HTMLSelectElement::value() const { update_cached_list_of_options(); for (auto const& option_element : m_cached_list_of_options) if (option_element->selected()) return option_element->value(); - return ""_string; + return {}; } -WebIDL::ExceptionOr HTMLSelectElement::set_value(String const& value) +WebIDL::ExceptionOr HTMLSelectElement::set_value(Utf16String const& value) { update_cached_list_of_options(); for (auto const& option_element : list_of_options()) @@ -478,7 +478,7 @@ void HTMLSelectElement::show_the_picker_if_applicable() for (auto const& child : opt_group_element->children_as_vector()) { if (auto const& option_element = as_if(*child)) { if (!option_element->has_attribute(Web::HTML::AttributeNames::hidden)) - option_group_items.append(SelectItemOption { id_counter++, option_element->selected(), option_element->disabled(), option_element, strip_newlines(option_element->label()), option_element->value() }); + option_group_items.append(SelectItemOption { id_counter++, option_element->selected(), option_element->disabled(), option_element, strip_newlines(option_element->label()), option_element->value().to_utf8_but_should_be_ported_to_utf16() }); } } m_select_items.append(SelectItemOptionGroup { opt_group_element->get_attribute(AttributeNames::label).value_or(String {}), option_group_items }); @@ -487,7 +487,7 @@ void HTMLSelectElement::show_the_picker_if_applicable() if (auto const& option_element = as_if(*child)) { if (!option_element->has_attribute(Web::HTML::AttributeNames::hidden)) - m_select_items.append(SelectItemOption { id_counter++, option_element->selected(), option_element->disabled(), option_element, strip_newlines(option_element->label()), option_element->value() }); + m_select_items.append(SelectItemOption { id_counter++, option_element->selected(), option_element->disabled(), option_element, strip_newlines(option_element->label()), option_element->value().to_utf8_but_should_be_ported_to_utf16() }); } if (auto const* hr_element = as_if(*child)) { diff --git a/Libraries/LibWeb/HTML/HTMLSelectElement.h b/Libraries/LibWeb/HTML/HTMLSelectElement.h index 859e85dfe29..7021b72e0f1 100644 --- a/Libraries/LibWeb/HTML/HTMLSelectElement.h +++ b/Libraries/LibWeb/HTML/HTMLSelectElement.h @@ -51,8 +51,8 @@ public: WebIDL::Long selected_index() const; void set_selected_index(WebIDL::Long); - virtual String value() const override; - WebIDL::ExceptionOr set_value(String const&); + virtual Utf16String value() const override; + WebIDL::ExceptionOr set_value(Utf16String const&); bool is_open() const { return m_is_open; } void set_is_open(bool); diff --git a/Libraries/LibWeb/HTML/HTMLSelectElement.idl b/Libraries/LibWeb/HTML/HTMLSelectElement.idl index cc53df48bc5..ddaaefee40b 100644 --- a/Libraries/LibWeb/HTML/HTMLSelectElement.idl +++ b/Libraries/LibWeb/HTML/HTMLSelectElement.idl @@ -29,7 +29,7 @@ interface HTMLSelectElement : HTMLElement { [SameObject] readonly attribute HTMLCollection selectedOptions; attribute long selectedIndex; - attribute DOMString value; + attribute Utf16DOMString value; readonly attribute boolean willValidate; readonly attribute ValidityState validity; diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index fbd5da1530a..c0b4be74b4d 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -114,10 +114,10 @@ void HTMLTextAreaElement::reset_algorithm() m_user_validity = false; m_dirty_value = false; // and the raw value to its child text content. - set_raw_value(child_text_content()); + set_raw_value(Utf16String::from_utf8(child_text_content())); if (m_text_node) { - m_text_node->set_text_content(m_raw_value); + m_text_node->set_text_content(m_raw_value.to_utf8_but_should_be_ported_to_utf16()); update_placeholder_visibility(); } } @@ -129,7 +129,7 @@ void HTMLTextAreaElement::clear_algorithm() m_dirty_value = false; // and set the raw value of element to an empty string. - set_raw_value(child_text_content()); + set_raw_value(Utf16String::from_utf8(child_text_content())); // Unlike their associated reset algorithms, changes made to form controls as part of these algorithms do count as // changes caused by the user (and thus, e.g. do cause input events to fire). @@ -169,14 +169,14 @@ void HTMLTextAreaElement::set_default_value(String const& default_value) } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-value -String HTMLTextAreaElement::value() const +Utf16String HTMLTextAreaElement::value() const { // The value IDL attribute must, on getting, return the element's API value. return api_value(); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-value -void HTMLTextAreaElement::set_value(String const& value) +void HTMLTextAreaElement::set_value(Utf16String const& value) { // 1. Let oldAPIValue be this element's API value. auto old_api_value = api_value(); @@ -191,7 +191,7 @@ void HTMLTextAreaElement::set_value(String const& value) // the text control, unselecting any selected text and resetting the selection direction to "none". if (api_value() != old_api_value) { if (m_text_node) { - m_text_node->set_data(Utf16String::from_utf8(m_raw_value)); + m_text_node->set_data(m_raw_value); update_placeholder_visibility(); set_the_selection_range(m_text_node->length(), m_text_node->length()); @@ -199,7 +199,7 @@ void HTMLTextAreaElement::set_value(String const& value) } } -void HTMLTextAreaElement::set_raw_value(String value) +void HTMLTextAreaElement::set_raw_value(Utf16String value) { auto old_raw_value = move(m_raw_value); m_raw_value = move(value); @@ -210,7 +210,7 @@ void HTMLTextAreaElement::set_raw_value(String value) } // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-fe-api-value-3 -String HTMLTextAreaElement::api_value() const +Utf16String HTMLTextAreaElement::api_value() const { // The algorithm for obtaining the element's API value is to return the element's raw value, with newlines normalized. if (!m_api_value.has_value()) @@ -221,7 +221,7 @@ String HTMLTextAreaElement::api_value() const // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value WebIDL::ExceptionOr HTMLTextAreaElement::set_relevant_value(Utf16String const& value) { - set_value(value.to_utf8_but_should_be_ported_to_utf16()); + set_value(value); return {}; } @@ -229,7 +229,7 @@ WebIDL::ExceptionOr HTMLTextAreaElement::set_relevant_value(Utf16String co u32 HTMLTextAreaElement::text_length() const { // The textLength IDL attribute must return the length of the element's API value. - return AK::utf16_code_unit_length_from_utf8(api_value()); + return api_value().length_in_code_units(); } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-willvalidate @@ -379,7 +379,7 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed() handle_readonly_attribute(attribute(HTML::AttributeNames::readonly)); // NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content. // Otherwise, it will get filled in whenever that does get called. - m_text_node->set_text_content(m_raw_value); + m_text_node->set_text_content(m_raw_value.to_utf8_but_should_be_ported_to_utf16()); handle_maxlength_attribute(); MUST(m_inner_text_element->append_child(*m_text_node)); @@ -430,9 +430,9 @@ void HTMLTextAreaElement::children_changed(ChildrenChangedMetadata const* metada // The children changed steps for textarea elements must, if the element's dirty value flag is false, // set the element's raw value to its child text content. if (!m_dirty_value) { - set_raw_value(child_text_content()); + set_raw_value(Utf16String::from_utf8(child_text_content())); if (m_text_node) - m_text_node->set_text_content(m_raw_value); + m_text_node->set_text_content(m_raw_value.to_utf8_but_should_be_ported_to_utf16()); update_placeholder_visibility(); } } @@ -452,7 +452,7 @@ void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString co void HTMLTextAreaElement::did_edit_text_node() { VERIFY(m_text_node); - set_raw_value(m_text_node->data().to_utf8_but_should_be_ported_to_utf16()); + set_raw_value(m_text_node->data()); // Any time the user causes the element's raw value to change, the user agent must queue an element task on the user // interaction task source given the textarea element to fire an event named input at the textarea element, with the diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h index ab2dea28fd2..6b43d5ce293 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h @@ -80,14 +80,14 @@ public: String default_value() const; void set_default_value(String const&); - String value() const override; - void set_value(String const&); + Utf16String value() const override; + void set_value(Utf16String const&); // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-fe-api-value-3 - String api_value() const; + Utf16String api_value() const; // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value - virtual Utf16String relevant_value() override { return Utf16String::from_utf8(api_value()); } + virtual Utf16String relevant_value() override { return api_value(); } virtual WebIDL::ExceptionOr set_relevant_value(Utf16String const& value) override; virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; } @@ -140,7 +140,7 @@ private: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; - void set_raw_value(String); + void set_raw_value(Utf16String); // ^DOM::Element virtual i32 default_tab_index_value() const override; @@ -169,10 +169,10 @@ private: bool m_user_validity { false }; // https://html.spec.whatwg.org/multipage/form-elements.html#concept-textarea-raw-value - String m_raw_value; + Utf16String m_raw_value; // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-api-value - mutable Optional m_api_value; + mutable Optional m_api_value; }; } diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl b/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl index 786ed392d35..a38e1eefd63 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl @@ -23,7 +23,7 @@ interface HTMLTextAreaElement : HTMLElement { readonly attribute DOMString type; [CEReactions] attribute DOMString defaultValue; - [LegacyNullToEmptyString] attribute DOMString value; + [LegacyNullToEmptyString] attribute Utf16DOMString value; readonly attribute unsigned long textLength; readonly attribute boolean willValidate; diff --git a/Libraries/LibWeb/HTML/Numbers.cpp b/Libraries/LibWeb/HTML/Numbers.cpp index 34f32856b83..7ca5fdeed98 100644 --- a/Libraries/LibWeb/HTML/Numbers.cpp +++ b/Libraries/LibWeb/HTML/Numbers.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -283,7 +284,7 @@ fraction_exit: value *= pow(10, exponent); } -conversion: { +conversion: // 15. Conversion: Let S be the set of finite IEEE 754 double-precision floating-point values except −0, // but with two special values added: 2^1024 and −2^1024. if (!isfinite(value)) { @@ -305,6 +306,14 @@ conversion: { // 18. Return rounded-value. return rounded_value; } + +// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values +Optional parse_floating_point_number(Utf16String const& string) +{ + // FIXME: Implement a UTF-16 GenericLexer. + if (!string.has_ascii_storage()) + return {}; + return parse_floating_point_number(string.ascii_view()); } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number @@ -340,6 +349,15 @@ bool is_valid_floating_point_number(StringView string) return lexer.tell_remaining() == 0; } +// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number +bool is_valid_floating_point_number(Utf16String const& string) +{ + // FIXME: Implement a UTF-16 GenericLexer. + if (!string.has_ascii_storage()) + return false; + return is_valid_floating_point_number(string.ascii_view()); +} + WebIDL::ExceptionOr convert_non_negative_integer_to_string(JS::Realm& realm, WebIDL::Long value) { if (value < 0) diff --git a/Libraries/LibWeb/HTML/Numbers.h b/Libraries/LibWeb/HTML/Numbers.h index 52b57b367c2..74b45e70c91 100644 --- a/Libraries/LibWeb/HTML/Numbers.h +++ b/Libraries/LibWeb/HTML/Numbers.h @@ -20,8 +20,10 @@ Optional parse_non_negative_integer(StringView string); Optional parse_non_negative_integer_digits(StringView string); Optional parse_floating_point_number(StringView string); +Optional parse_floating_point_number(Utf16String const& string); bool is_valid_floating_point_number(StringView string); +bool is_valid_floating_point_number(Utf16String const& string); WebIDL::ExceptionOr convert_non_negative_integer_to_string(JS::Realm&, WebIDL::Long); diff --git a/Libraries/LibWeb/Infra/Strings.cpp b/Libraries/LibWeb/Infra/Strings.cpp index 11c4ff45b17..eb5cb41337a 100644 --- a/Libraries/LibWeb/Infra/Strings.cpp +++ b/Libraries/LibWeb/Infra/Strings.cpp @@ -43,6 +43,30 @@ String normalize_newlines(String const& string) return MUST(builder.to_string()); } +// https://infra.spec.whatwg.org/#normalize-newlines +Utf16String normalize_newlines(Utf16String const& string) +{ + // To normalize newlines in a string, replace every U+000D CR U+000A LF code point pair with a single U+000A LF + // code point, and then replace every remaining U+000D CR code point with a U+000A LF code point. + if (!string.contains('\r')) + return string; + + // FIXME: Implement a UTF-16 GenericLexer. + StringBuilder builder(StringBuilder::Mode::UTF16, string.length_in_code_units()); + + for (size_t i = 0; i < string.length_in_code_units(); ++i) { + if (auto code_unit = string.code_unit_at(i); code_unit == '\r') { + if (i + 1 < string.length_in_code_units() && string.code_unit_at(i + 1) == '\n') + ++i; + builder.append('\n'); + } else { + builder.append_code_unit(code_unit); + } + } + + return builder.to_utf16_string(); +} + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace ErrorOr strip_and_collapse_whitespace(StringView string) { diff --git a/Libraries/LibWeb/Infra/Strings.h b/Libraries/LibWeb/Infra/Strings.h index 839c74af107..d61e2b19bde 100644 --- a/Libraries/LibWeb/Infra/Strings.h +++ b/Libraries/LibWeb/Infra/Strings.h @@ -14,6 +14,7 @@ namespace Web::Infra { String normalize_newlines(String const&); +Utf16String normalize_newlines(Utf16String const&); ErrorOr strip_and_collapse_whitespace(StringView string); bool is_code_unit_prefix(StringView potential_prefix, StringView input); ErrorOr convert_to_scalar_value_string(StringView string); diff --git a/Services/WebContent/WebDriverConnection.cpp b/Services/WebContent/WebDriverConnection.cpp index d509640fd2f..211194d3370 100644 --- a/Services/WebContent/WebDriverConnection.cpp +++ b/Services/WebContent/WebDriverConnection.cpp @@ -1951,7 +1951,7 @@ Web::WebDriver::Response WebDriverConnection::element_send_keys_impl(StringView return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element is immutable"sv); // 3. Set a property value to text on element. - MUST(input_element.set_value(text)); + MUST(input_element.set_value(Utf16String::from_utf8(text))); // FIXME: 4. If element is suffering from bad input return an error with error code invalid argument. diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-patternMismatch.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-patternMismatch.txt index c24cc7007e6..4346c5b323b 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-patternMismatch.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-patternMismatch.txt @@ -2,8 +2,8 @@ Harness status: OK Found 85 tests -71 Pass -14 Fail +78 Pass +7 Fail Pass [INPUT in TEXT status] The pattern attribute is not set Pass [INPUT in TEXT status] The value attibute is empty string Pass [INPUT in TEXT status] The value attribute matches the pattern attribute @@ -13,7 +13,7 @@ Pass [INPUT in TEXT status] The value attribute mismatches the pattern attribute Pass [INPUT in TEXT status] Invalid regular expression gets ignored Pass [INPUT in TEXT status] Invalid `v` regular expression gets ignored Pass [INPUT in TEXT status] The pattern attribute tries to escape a group -Fail [INPUT in TEXT status] The pattern attribute uses Unicode features +Pass [INPUT in TEXT status] The pattern attribute uses Unicode features Pass [INPUT in TEXT status] The value attribute matches JavaScript-specific regular expression Fail [INPUT in TEXT status] The value attribute mismatches JavaScript-specific regular expression Pass [INPUT in SEARCH status] The pattern attribute is not set @@ -25,7 +25,7 @@ Pass [INPUT in SEARCH status] The value attribute mismatches the pattern attribu Pass [INPUT in SEARCH status] Invalid regular expression gets ignored Pass [INPUT in SEARCH status] Invalid `v` regular expression gets ignored Pass [INPUT in SEARCH status] The pattern attribute tries to escape a group -Fail [INPUT in SEARCH status] The pattern attribute uses Unicode features +Pass [INPUT in SEARCH status] The pattern attribute uses Unicode features Pass [INPUT in SEARCH status] The value attribute matches JavaScript-specific regular expression Fail [INPUT in SEARCH status] The value attribute mismatches JavaScript-specific regular expression Pass [INPUT in TEL status] The pattern attribute is not set @@ -37,7 +37,7 @@ Pass [INPUT in TEL status] The value attribute mismatches the pattern attribute Pass [INPUT in TEL status] Invalid regular expression gets ignored Pass [INPUT in TEL status] Invalid `v` regular expression gets ignored Pass [INPUT in TEL status] The pattern attribute tries to escape a group -Fail [INPUT in TEL status] The pattern attribute uses Unicode features +Pass [INPUT in TEL status] The pattern attribute uses Unicode features Pass [INPUT in TEL status] The value attribute matches JavaScript-specific regular expression Fail [INPUT in TEL status] The value attribute mismatches JavaScript-specific regular expression Pass [INPUT in URL status] The pattern attribute is not set @@ -49,7 +49,7 @@ Pass [INPUT in URL status] The value attribute mismatches the pattern attribute Pass [INPUT in URL status] Invalid regular expression gets ignored Pass [INPUT in URL status] Invalid `v` regular expression gets ignored Pass [INPUT in URL status] The pattern attribute tries to escape a group -Fail [INPUT in URL status] The pattern attribute uses Unicode features +Pass [INPUT in URL status] The pattern attribute uses Unicode features Pass [INPUT in URL status] The value attribute matches JavaScript-specific regular expression Fail [INPUT in URL status] The value attribute mismatches JavaScript-specific regular expression Pass [INPUT in EMAIL status] The pattern attribute is not set @@ -61,7 +61,7 @@ Pass [INPUT in EMAIL status] The value attribute mismatches the pattern attribut Pass [INPUT in EMAIL status] Invalid regular expression gets ignored Pass [INPUT in EMAIL status] Invalid `v` regular expression gets ignored Pass [INPUT in EMAIL status] The pattern attribute tries to escape a group -Fail [INPUT in EMAIL status] The pattern attribute uses Unicode features +Pass [INPUT in EMAIL status] The pattern attribute uses Unicode features Pass [INPUT in EMAIL status] The value attribute matches JavaScript-specific regular expression Fail [INPUT in EMAIL status] The value attribute mismatches JavaScript-specific regular expression Pass [INPUT in PASSWORD status] The pattern attribute is not set @@ -73,7 +73,7 @@ Pass [INPUT in PASSWORD status] The value attribute mismatches the pattern attri Pass [INPUT in PASSWORD status] Invalid regular expression gets ignored Pass [INPUT in PASSWORD status] Invalid `v` regular expression gets ignored Pass [INPUT in PASSWORD status] The pattern attribute tries to escape a group -Fail [INPUT in PASSWORD status] The pattern attribute uses Unicode features +Pass [INPUT in PASSWORD status] The pattern attribute uses Unicode features Pass [INPUT in PASSWORD status] The value attribute matches JavaScript-specific regular expression Fail [INPUT in PASSWORD status] The value attribute mismatches JavaScript-specific regular expression Pass [INPUT in EMAIL status] The pattern attribute is not set, if multiple is present @@ -85,7 +85,7 @@ Pass [INPUT in EMAIL status] The value attribute mismatches the pattern attribut Pass [INPUT in EMAIL status] Invalid regular expression gets ignored, if multiple is present Pass [INPUT in EMAIL status] Invalid `v` regular expression gets ignored, if multiple is present Pass [INPUT in EMAIL status] The pattern attribute tries to escape a group, if multiple is present -Fail [INPUT in EMAIL status] The pattern attribute uses Unicode features, if multiple is present +Pass [INPUT in EMAIL status] The pattern attribute uses Unicode features, if multiple is present Pass [INPUT in EMAIL status] The value attribute matches JavaScript-specific regular expression, if multiple is present Fail [INPUT in EMAIL status] The value attribute mismatches JavaScript-specific regular expression, if multiple is present Pass [INPUT in EMAIL status] Commas should be stripped from regex input, if multiple is present \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/pattern_attribute.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/pattern_attribute.txt index c90c858f2e1..a7e804993f0 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/pattern_attribute.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/pattern_attribute.txt @@ -2,9 +2,8 @@ Harness status: OK Found 4 tests -3 Pass -1 Fail +4 Pass Pass basic support -Fail is Unicode code point-aware +Pass is Unicode code point-aware Pass supports Unicode property escape syntax Pass supports Unicode property escape syntax for properties of strings \ No newline at end of file