Browse Source

LibWeb: Add “valid floating-point number” for HTMLInputElement.value

This change adds checking for the following spec requirements:

- https://html.spec.whatwg.org/#number-state-(type=number):value-sanitization-algorithm
- https://html.spec.whatwg.org/#range-state-(type=range):value-sanitization-algorithm

That is, it adds checking that HTMLInputElement.value is what the spec
defines as a “valid floating-point number” when the “type” attribute for
the HTMLInputElement is either “number” or “range”.

This change causes Ladybird to pass all the failing tests at
https://wpt.fyi/results/html/semantics/forms/the-input-element/number.html?run_id=5080423051034624
and to match the relevant behavior in Webkit, Blink, and Gecko.

Otherwise, without this change, Ladybird fails those tests, and the
relevant Ladybird behavior isn’t interoperable with other engines.
sideshowbarker 10 months ago
parent
commit
e76e48421f

+ 27 - 0
Tests/LibWeb/TestNumbers.cpp

@@ -103,4 +103,31 @@ TEST_CASE(parse_non_negative_integer)
 
     optional_value = Web::HTML::parse_non_negative_integer("-3"sv);
     EXPECT(!optional_value.has_value());
+
+    EXPECT(Web::HTML::is_valid_floating_point_number("11"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("11.12"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("-11111"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("-11111.123"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("1e2"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("1E2"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("1e+2"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("1d+2"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("foobar"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number(".1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("1."sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("-0"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("Infinity"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("-Infinity"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("NaN"sv));
+    EXPECT(Web::HTML::is_valid_floating_point_number("9007199254740993"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("1e"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("+1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("+"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("-"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("\t1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("\n1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("\f1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("\r1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number(" 1"sv));
+    EXPECT(!Web::HTML::is_valid_floating_point_number("1trailing junk"sv));
 }

+ 10 - 2
Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp

@@ -1373,8 +1373,14 @@ String HTMLInputElement::value_sanitization_algorithm(String const& value) const
             return MUST(String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE)));
         }
     } else 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.
+        // 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.
+        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) {
@@ -1402,7 +1408,9 @@ String HTMLInputElement::value_sanitization_algorithm(String const& value) const
         // 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 (!maybe_value.has_value() || !isfinite(maybe_value.value())) {
+        if (!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.
             auto minimum = *min();
             auto maximum = *max();

+ 33 - 0
Userland/Libraries/LibWeb/HTML/Numbers.cpp

@@ -92,6 +92,39 @@ Optional<double> parse_floating_point_number(StringView string)
     return maybe_double.value();
 }
 
+// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number
+bool is_valid_floating_point_number(StringView string)
+{
+    GenericLexer lexer { string };
+    // 1. Optionally, a U+002D HYPHEN-MINUS character (-).
+    lexer.consume_specific('-');
+    // 2. One or both of the following, in the given order:
+    // 2.1. A series of one or more ASCII digits.
+    bool has_leading_digits = !lexer.consume_while(is_ascii_digit).is_empty();
+    // 2.2. Both of the following, in the given order:
+    // 2.2.1. A single U+002E FULL STOP character (.).
+    if (lexer.consume_specific('.')) {
+        // 2.2.2. A series of one or more ASCII digits.
+        if (lexer.consume_while(is_ascii_digit).is_empty())
+            return false;
+    } else if (!has_leading_digits) {
+        // Doesn’t begin with digits, doesn’t begin with a full stop followed by digits.
+        return false;
+    }
+    // 3. Optionally:
+    // 3.1. Either a U+0065 LATIN SMALL LETTER E character (e) or a U+0045 LATIN CAPITAL
+    //      LETTER E character (E).
+    if (lexer.consume_specific('e') || lexer.consume_specific('E')) {
+        // 3.2. Optionally, a U+002D HYPHEN-MINUS character (-) or U+002B PLUS SIGN
+        //      character (+).
+        lexer.consume_specific('-') || lexer.consume_specific('+');
+        // 3.3. A series of one or more ASCII digits.
+        if (lexer.consume_while(is_ascii_digit).is_empty())
+            return false;
+    }
+    return lexer.tell_remaining() == 0;
+}
+
 WebIDL::ExceptionOr<String> convert_non_negative_integer_to_string(JS::Realm& realm, WebIDL::Long value)
 {
     if (value < 0)

+ 2 - 0
Userland/Libraries/LibWeb/HTML/Numbers.h

@@ -19,6 +19,8 @@ Optional<u32> parse_non_negative_integer(StringView string);
 
 Optional<double> parse_floating_point_number(StringView string);
 
+bool is_valid_floating_point_number(StringView string);
+
 WebIDL::ExceptionOr<String> convert_non_negative_integer_to_string(JS::Realm&, WebIDL::Long);
 
 }