From 6e257897f6add729500fa577bbc165dc3d98ed9a Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Fri, 7 Feb 2025 17:24:35 +1300 Subject: [PATCH] LibWeb/HTML: Implement element valueAsNumber for 'week' One point to note is that I am not entirely sure what the result of the pre-existing valueAsNumber test should be for this strange case which does not lie exactly on a week/day boundary. Chrome gives a negative timestamp, which seems more wrong than the result we give, and neither gecko or WebKit appear to support the 'week' type. So I'm considering this result acceptable for now, and this may be something that will need more WPT tests added in the future. --- Libraries/LibWeb/HTML/Dates.cpp | 55 +++++++++++++++++++ Libraries/LibWeb/HTML/Dates.h | 7 +++ Libraries/LibWeb/HTML/HTMLInputElement.cpp | 50 +++++++++++++++++ .../HTML/HTMLInputElement-valueAsNumber.txt | 2 +- .../the-input-element/input-valueasnumber.txt | 14 ++--- 5 files changed, 120 insertions(+), 8 deletions(-) diff --git a/Libraries/LibWeb/HTML/Dates.cpp b/Libraries/LibWeb/HTML/Dates.cpp index 7b6532053d0..2faaf3066c0 100644 --- a/Libraries/LibWeb/HTML/Dates.cpp +++ b/Libraries/LibWeb/HTML/Dates.cpp @@ -308,4 +308,59 @@ i32 number_of_months_since_unix_epoch(YearAndMonth year_and_month) return (year_and_month.year - 1970) * 12 + year_and_month.month - 1; } +// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-week-string +Optional parse_a_week_string(StringView input_view) +{ + // 1. Let input be the string being parsed. + // 2. Let position be a pointer into input, initially pointing at the start of the string. + GenericLexer input { input_view }; + + // 3. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is + // not at least four characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. + // Let that number be the year. + auto year_string = input.consume_while(is_ascii_digit); + if (year_string.length() < 4) + return {}; + auto maybe_year = year_string.to_number(); + if (!maybe_year.has_value()) + return {}; + auto year = maybe_year.value(); + + // 4. If year is not a number greater than zero, then fail. + if (year < 1) + return {}; + + // 5. If position is beyond the end of input or if the character at position is not a U+002D HYPHEN-MINUS character, then + // fail. Otherwise, move position forwards one character. + if (!input.consume_specific('-')) + return {}; + + // 6. If position is beyond the end of input or if the character at position is not a U+0057 LATIN CAPITAL LETTER W character + // (W), then fail. Otherwise, move position forwards one character. + if (!input.consume_specific('W')) + return {}; + + // 7. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence is not + // exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer. Let that + // number be the week. + auto week_string = input.consume_while(is_ascii_digit); + if (week_string.length() != 2) + return {}; + auto week = week_string.to_number().value(); + + // 8. Let maxweek be the week number of the last day of year year. + auto maxweek = week_number_of_the_last_day(year); + + // 9. If week is not a number in the range 1 ≤ week ≤ maxweek, then fail. + if (week < 1 || week > maxweek) + return {}; + + // 10. If position is not beyond the end of input, then fail. + if (!input.is_eof()) + return {}; + + // 11. Return the week-year number year and the week number week. + return WeekYearAndWeek { year, week }; +} + } diff --git a/Libraries/LibWeb/HTML/Dates.h b/Libraries/LibWeb/HTML/Dates.h index f6e1607ff20..6f617be969e 100644 --- a/Libraries/LibWeb/HTML/Dates.h +++ b/Libraries/LibWeb/HTML/Dates.h @@ -28,6 +28,13 @@ struct YearAndMonth { u32 month; }; Optional parse_a_month_string(StringView); + +struct WeekYearAndWeek { + u32 week_year; + u32 week; +}; +Optional parse_a_week_string(StringView); + i32 number_of_months_since_unix_epoch(YearAndMonth); } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 80475ea66f9..2c8489e913e 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -2079,6 +2079,20 @@ static Optional convert_month_string_to_number(StringView input) return number_of_months_since_unix_epoch(maybe_year_and_month.value()); } +// https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):concept-input-value-string-number +static Optional convert_week_string_to_number(StringView input) +{ + // The algorithm to convert a string to a number, given a string input, is as follows: If parsing a week + // string from input results in an error, then return an error; otherwise, return the number of + // milliseconds elapsed from midnight UTC on the morning of 1970-01-01 (the time represented by the value + // "1970-01-01T00:00:00.0Z") to midnight UTC on the morning of the Monday of the parsed week, ignoring + // leap seconds. + auto parsed_week = parse_a_week_string(input); + if (!parsed_week.has_value()) + return {}; + return UnixDateTime::from_iso8601_week(parsed_week->week_year, parsed_week->week).milliseconds_since_epoch(); +} + // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number Optional HTMLInputElement::convert_string_to_number(StringView input) const { @@ -2093,6 +2107,9 @@ Optional HTMLInputElement::convert_string_to_number(StringView input) co if (type_state() == TypeAttributeState::Month) return convert_month_string_to_number(input); + if (type_state() == TypeAttributeState::Week) + return convert_week_string_to_number(input); + dbgln("HTMLInputElement::convert_string_to_number() not implemented for input type {}", type()); return {}; } @@ -2108,6 +2125,36 @@ static String convert_number_to_month_string(double input) return MUST(String::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) +{ + // 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 + // (the time represented by the value "1970-01-01T00:00:00.0Z"). + + int days_since_epoch = static_cast(input / AK::ms_per_day); + int year = 1970; + + while (true) { + auto days = days_in_year(year); + if (days_since_epoch < days) + break; + days_since_epoch -= days; + ++year; + } + + auto january_1_weekday = day_of_week(year, 1, 1); + int offset_to_week_start = (january_1_weekday <= 3) ? january_1_weekday : january_1_weekday - 7; + int week = (days_since_epoch + offset_to_week_start) / 7 + 1; + + if (week < 0) { + --year; + week = weeks_in_year(year) + week; + } + + return MUST(String::formatted("{:04d}-W{:02d}", year, week)); +} + // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number String HTMLInputElement::convert_number_to_string(double input) const { @@ -2122,6 +2169,9 @@ String HTMLInputElement::convert_number_to_string(double input) const if (type_state() == TypeAttributeState::Month) return convert_number_to_month_string(input); + if (type_state() == TypeAttributeState::Week) + return convert_number_to_week_string(input); + dbgln("HTMLInputElement::convert_number_to_string() not implemented for input type {}", type()); return {}; } diff --git a/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt b/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt index 4ed2fd35b5e..2961a595402 100644 --- a/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt +++ b/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt @@ -33,7 +33,7 @@ email threw exception: InvalidStateError: valueAsNumber: Invalid input type used password threw exception: InvalidStateError: valueAsNumber: Invalid input type used date did not throw: NaN month did not throw: 100 -week did not throw: NaN +week did not throw: 345600000 time did not throw: NaN datetime-local did not throw: NaN color threw exception: InvalidStateError: valueAsNumber: Invalid input type used diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt index 403b16c7531..541bd3bf986 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt @@ -2,8 +2,8 @@ Harness status: OK Found 60 tests -38 Pass -22 Fail +43 Pass +17 Fail Pass valueAsNumber getter on type date (actual value: , expected valueAsNumber: NaN) Pass valueAsNumber getter on type date (actual value: 0000-12-10, expected valueAsNumber: NaN) Pass valueAsNumber getter on type date (actual value: 2019-00-12, expected valueAsNumber: NaN) @@ -26,11 +26,11 @@ Pass valueAsNumber getter on type week (actual value: , expected valueAsNumber: Pass valueAsNumber getter on type week (actual value: 0000-W50, expected valueAsNumber: NaN) Pass valueAsNumber getter on type week (actual value: 2019-W00, expected valueAsNumber: NaN) Pass valueAsNumber getter on type week (actual value: 2019-W60, expected valueAsNumber: NaN) -Fail valueAsNumber getter on type week (actual value: 2019-W50, expected valueAsNumber: 1575849600000) -Fail valueAsNumber getter on type week (actual value: 1969-W20, expected valueAsNumber: -20217600000) -Fail valueAsNumber setter on type week (actual valueAsNumber: 0, expected value: 1970-W01) -Fail valueAsNumber setter on type week (actual valueAsNumber: 1575849600000, expected value: 2019-W50) -Fail valueAsNumber setter on type week (actual valueAsNumber: -20217600000, expected value: 1969-W20) +Pass valueAsNumber getter on type week (actual value: 2019-W50, expected valueAsNumber: 1575849600000) +Pass valueAsNumber getter on type week (actual value: 1969-W20, expected valueAsNumber: -20217600000) +Pass valueAsNumber setter on type week (actual valueAsNumber: 0, expected value: 1970-W01) +Pass valueAsNumber setter on type week (actual valueAsNumber: 1575849600000, expected value: 2019-W50) +Pass valueAsNumber setter on type week (actual valueAsNumber: -20217600000, expected value: 1969-W20) Pass valueAsNumber getter on type time (actual value: , expected valueAsNumber: NaN) Pass valueAsNumber getter on type time (actual value: 24:00, expected valueAsNumber: NaN) Pass valueAsNumber getter on type time (actual value: 00:60, expected valueAsNumber: NaN)