mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
script: Use time@0.3
for input elements and do conversion in a &str trait (#33355)
This changes converts all input element parsing and normalization to use `time` instead of `chrono`. `time` is used by our dependencies, so it makes sense to work toward removing the Servo dependency on chrono. In addition, parsing and normalization also moves to a trait on &str to prepare for the possibility of all script parsers moving to a separate crate that can have unit tests written against it. Code duplication is eliminated when possible and more conversion is done using integer types. These two things together mean we pass more tests now. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
687f356db9
commit
8842fe9df5
5 changed files with 528 additions and 482 deletions
|
@ -4,12 +4,11 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::Cell;
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Range;
|
||||
use std::ptr::NonNull;
|
||||
use std::{f64, ptr};
|
||||
|
||||
use chrono::naive::{NaiveDate, NaiveDateTime};
|
||||
use chrono::{DateTime, Datelike, Weekday};
|
||||
use dom_struct::dom_struct;
|
||||
use embedder_traits::{FilterPattern, InputMethodType};
|
||||
use encoding_rs::Encoding;
|
||||
|
@ -30,9 +29,11 @@ use servo_atoms::Atom;
|
|||
use style::attr::AttrValue;
|
||||
use style::str::{split_commas, str_join};
|
||||
use style_dom::ElementState;
|
||||
use time_03::{Month, OffsetDateTime, Time};
|
||||
use unicode_bidi::{bidi_class, BidiClass};
|
||||
use url::Url;
|
||||
|
||||
use super::bindings::str::{FromInputValueString, ToInputValueString};
|
||||
use crate::dom::activation::Activatable;
|
||||
use crate::dom::attr::Attr;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
|
@ -808,11 +809,9 @@ impl HTMLInputElement {
|
|||
// https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch-2
|
||||
InputType::Email => {
|
||||
if self.Multiple() {
|
||||
!split_commas(value).all(|s| {
|
||||
DOMString::from_string(s.to_string()).is_valid_email_address_string()
|
||||
})
|
||||
!split_commas(value).all(|string| string.is_valid_email_address_string())
|
||||
} else {
|
||||
!value.is_valid_email_address_string()
|
||||
!value.str().is_valid_email_address_string()
|
||||
}
|
||||
},
|
||||
// Other input types don't suffer from type mismatch
|
||||
|
@ -863,20 +862,20 @@ impl HTMLInputElement {
|
|||
false
|
||||
},
|
||||
// https://html.spec.whatwg.org/multipage/#date-state-(type%3Ddate)%3Asuffering-from-bad-input
|
||||
InputType::Date => !value.is_valid_date_string(),
|
||||
InputType::Date => !value.str().is_valid_date_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#month-state-(type%3Dmonth)%3Asuffering-from-bad-input
|
||||
InputType::Month => !value.is_valid_month_string(),
|
||||
InputType::Month => !value.str().is_valid_month_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#week-state-(type%3Dweek)%3Asuffering-from-bad-input
|
||||
InputType::Week => !value.is_valid_week_string(),
|
||||
InputType::Week => !value.str().is_valid_week_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#time-state-(type%3Dtime)%3Asuffering-from-bad-input
|
||||
InputType::Time => !value.is_valid_time_string(),
|
||||
InputType::Time => !value.str().is_valid_time_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#local-date-and-time-state-(type%3Ddatetime-local)%3Asuffering-from-bad-input
|
||||
InputType::DatetimeLocal => value.parse_local_date_and_time_string().is_none(),
|
||||
InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#number-state-(type%3Dnumber)%3Asuffering-from-bad-input
|
||||
// https://html.spec.whatwg.org/multipage/#range-state-(type%3Drange)%3Asuffering-from-bad-input
|
||||
InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
|
||||
// https://html.spec.whatwg.org/multipage/#color-state-(type%3Dcolor)%3Asuffering-from-bad-input
|
||||
InputType::Color => !value.is_valid_simple_color_string(),
|
||||
InputType::Color => !value.str().is_valid_simple_color_string(),
|
||||
// Other input types don't suffer from bad input
|
||||
_ => false,
|
||||
}
|
||||
|
@ -1305,9 +1304,9 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
|||
#[allow(unsafe_code)]
|
||||
fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
|
||||
self.convert_string_to_naive_datetime(self.Value())
|
||||
.map(|dt| unsafe {
|
||||
.map(|date_time| unsafe {
|
||||
let time = ClippedTime {
|
||||
t: dt.and_utc().timestamp_millis() as f64,
|
||||
t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
|
||||
};
|
||||
NonNull::new_unchecked(NewDateObject(*cx, time))
|
||||
})
|
||||
|
@ -1342,15 +1341,11 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
|||
return self.SetValue(DOMString::from(""));
|
||||
}
|
||||
}
|
||||
// now we make a Rust date out of it so we can use safe code for the
|
||||
// actual conversion logic
|
||||
match milliseconds_to_datetime(msecs) {
|
||||
Ok(dt) => match self.convert_naive_datetime_to_string(dt) {
|
||||
Ok(converted) => self.SetValue(converted),
|
||||
_ => self.SetValue(DOMString::from("")),
|
||||
},
|
||||
_ => self.SetValue(DOMString::from("")),
|
||||
}
|
||||
|
||||
let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
|
||||
return self.SetValue(DOMString::from(""));
|
||||
};
|
||||
self.SetValue(self.convert_datetime_to_dom_string(date_time))
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber
|
||||
|
@ -1367,14 +1362,13 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
|||
Err(Error::InvalidState)
|
||||
} else if value.is_nan() {
|
||||
self.SetValue(DOMString::from(""))
|
||||
} else if let Ok(converted) = self.convert_number_to_string(value) {
|
||||
} else if let Some(converted) = self.convert_number_to_string(value) {
|
||||
self.SetValue(converted)
|
||||
} else {
|
||||
// The most literal spec-compliant implementation would
|
||||
// use bignum chrono types so overflow is impossible,
|
||||
// but just setting an overflow to the empty string matches
|
||||
// Firefox's behavior.
|
||||
// (for example, try input.valueAsNumber=1e30 on a type="date" input)
|
||||
// The most literal spec-compliant implementation would use bignum types so
|
||||
// overflow is impossible, but just setting an overflow to the empty string
|
||||
// matches Firefox's behavior. For example, try input.valueAsNumber=1e30 on
|
||||
// a type="date" input.
|
||||
self.SetValue(DOMString::from(""))
|
||||
}
|
||||
}
|
||||
|
@ -1918,38 +1912,40 @@ impl HTMLInputElement {
|
|||
value.strip_leading_and_trailing_ascii_whitespace();
|
||||
},
|
||||
InputType::Date => {
|
||||
if !value.is_valid_date_string() {
|
||||
if !value.str().is_valid_date_string() {
|
||||
value.clear();
|
||||
}
|
||||
},
|
||||
InputType::Month => {
|
||||
if !value.is_valid_month_string() {
|
||||
if !value.str().is_valid_month_string() {
|
||||
value.clear();
|
||||
}
|
||||
},
|
||||
InputType::Week => {
|
||||
if !value.is_valid_week_string() {
|
||||
if !value.str().is_valid_week_string() {
|
||||
value.clear();
|
||||
}
|
||||
},
|
||||
InputType::Color => {
|
||||
if value.is_valid_simple_color_string() {
|
||||
if value.str().is_valid_simple_color_string() {
|
||||
value.make_ascii_lowercase();
|
||||
} else {
|
||||
*value = "#000000".into();
|
||||
}
|
||||
},
|
||||
InputType::Time => {
|
||||
if !value.is_valid_time_string() {
|
||||
if !value.str().is_valid_time_string() {
|
||||
value.clear();
|
||||
}
|
||||
},
|
||||
InputType::DatetimeLocal => {
|
||||
if value
|
||||
.convert_valid_normalized_local_date_and_time_string()
|
||||
.is_none()
|
||||
match value
|
||||
.str()
|
||||
.parse_local_date_time_string()
|
||||
.map(|date_time| date_time.to_local_date_time_string())
|
||||
{
|
||||
value.clear();
|
||||
Some(normalized_string) => *value = DOMString::from_string(normalized_string),
|
||||
None => value.clear(),
|
||||
}
|
||||
},
|
||||
InputType::Number => {
|
||||
|
@ -2112,44 +2108,55 @@ impl HTMLInputElement {
|
|||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#concept-input-value-string-number
|
||||
/// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-number>
|
||||
fn convert_string_to_number(&self, value: &DOMString) -> Option<f64> {
|
||||
match self.input_type() {
|
||||
InputType::Date => value
|
||||
.parse_date_string()
|
||||
.and_then(|(year, month, day)| NaiveDate::from_ymd_opt(year, month, day))
|
||||
.and_then(|date| date.and_hms_opt(0, 0, 0))
|
||||
.map(|time| time.and_utc().timestamp_millis() as f64),
|
||||
InputType::Month => match value.parse_month_string() {
|
||||
// This one returns number of months, not milliseconds
|
||||
// (specification requires this, presumably because number of
|
||||
// milliseconds is not consistent across months)
|
||||
// the - 1.0 is because january is 1, not 0
|
||||
Some((year, month)) => Some(((year - 1970) * 12) as f64 + (month as f64 - 1.0)),
|
||||
_ => None,
|
||||
},
|
||||
InputType::Week => value
|
||||
.parse_week_string()
|
||||
.and_then(|(year, weeknum)| NaiveDate::from_isoywd_opt(year, weeknum, Weekday::Mon))
|
||||
.and_then(|date| date.and_hms_opt(0, 0, 0))
|
||||
.map(|time| time.and_utc().timestamp_millis() as f64),
|
||||
InputType::Time => match value.parse_time_string() {
|
||||
Some((hours, minutes, seconds)) => {
|
||||
Some((seconds + 60.0 * minutes as f64 + 3600.0 * hours as f64) * 1000.0)
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
// > The algorithm to convert a string to a number, given a string input, is as
|
||||
// > follows: If parsing a date 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 parsed
|
||||
// > date, ignoring leap seconds.
|
||||
InputType::Date => value.str().parse_date_string().map(|date_time| {
|
||||
(date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
|
||||
}),
|
||||
// > The algorithm to convert a string to a number, given a string input, is as
|
||||
// > follows: If parsing a month from input results in an error, then return an
|
||||
// > error; otherwise, return the number of months between January 1970 and the
|
||||
// > parsed month.
|
||||
//
|
||||
// This one returns number of months, not milliseconds (specification requires
|
||||
// this, presumably because number of milliseconds is not consistent across
|
||||
// months) the - 1.0 is because january is 1, not 0
|
||||
InputType::Month => value.str().parse_month_string().map(|date_time| {
|
||||
((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
|
||||
}),
|
||||
// > 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.
|
||||
InputType::Week => value.str().parse_week_string().map(|date_time| {
|
||||
(date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
|
||||
}),
|
||||
// > The algorithm to convert a string to a number, given a string input, is as
|
||||
// > follows: If parsing a time from input results in an error, then return an
|
||||
// > error; otherwise, return the number of milliseconds elapsed from midnight to
|
||||
// > the parsed time on a day with no time changes.
|
||||
InputType::Time => value
|
||||
.str()
|
||||
.parse_time_string()
|
||||
.map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
|
||||
// > The algorithm to convert a string to a number, given a string input, is as
|
||||
// > follows: If parsing a date and time from input results in an error, then
|
||||
// > return an error; otherwise, return the number of milliseconds elapsed from
|
||||
// > midnight on the morning of 1970-01-01 (the time represented by the value
|
||||
// > "1970-01-01T00:00:00.0") to the parsed local date and time, ignoring leap
|
||||
// > seconds.
|
||||
InputType::DatetimeLocal => {
|
||||
// Is this supposed to know the locale's daylight-savings-time rules?
|
||||
value.parse_local_date_and_time_string().and_then(|date| {
|
||||
let seconds = date.seconds as u32;
|
||||
let milliseconds = ((date.seconds - seconds as f64) * 1000.) as u32;
|
||||
Some(
|
||||
NaiveDate::from_ymd_opt(date.year, date.month, date.day)?
|
||||
.and_hms_milli_opt(date.hour, date.minute, seconds, milliseconds)?
|
||||
.and_utc()
|
||||
.timestamp_millis() as f64,
|
||||
)
|
||||
value.str().parse_local_date_time_string().map(|date_time| {
|
||||
(date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
|
||||
})
|
||||
},
|
||||
InputType::Number | InputType::Range => value.parse_floating_point_number(),
|
||||
|
@ -2159,94 +2166,73 @@ impl HTMLInputElement {
|
|||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#concept-input-value-string-number
|
||||
fn convert_number_to_string(&self, value: f64) -> Result<DOMString, ()> {
|
||||
/// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-number>
|
||||
fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
|
||||
match self.input_type() {
|
||||
InputType::Date => {
|
||||
let datetime = milliseconds_to_datetime(value)?;
|
||||
Ok(DOMString::from(datetime.format("%Y-%m-%d").to_string()))
|
||||
InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
|
||||
OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
|
||||
.ok()
|
||||
.map(|value| self.convert_datetime_to_dom_string(value))
|
||||
},
|
||||
InputType::Month => {
|
||||
// interpret value as months(not millis) in epoch, return monthstring
|
||||
let year_from_1970 = (value / 12.0).floor();
|
||||
let month = (value - year_from_1970 * 12.0).floor() as u32 + 1; // january is 1, not 0
|
||||
let year = (year_from_1970 + 1970.0) as u64;
|
||||
Ok(DOMString::from(format!("{:04}-{:02}", year, month)))
|
||||
},
|
||||
InputType::Week => {
|
||||
let datetime = milliseconds_to_datetime(value)?;
|
||||
let year = datetime.iso_week().year(); // not necessarily the same as datetime.year()
|
||||
let week = datetime.iso_week().week();
|
||||
Ok(DOMString::from(format!("{:04}-W{:02}", year, week)))
|
||||
},
|
||||
InputType::Time => {
|
||||
let datetime = milliseconds_to_datetime(value)?;
|
||||
Ok(DOMString::from(datetime.format("%H:%M:%S%.3f").to_string()))
|
||||
},
|
||||
InputType::DatetimeLocal => {
|
||||
let datetime = milliseconds_to_datetime(value)?;
|
||||
Ok(DOMString::from(
|
||||
datetime.format("%Y-%m-%dT%H:%M:%S%.3f").to_string(),
|
||||
))
|
||||
// > 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.
|
||||
let date = OffsetDateTime::UNIX_EPOCH;
|
||||
let years = (value / 12.) as i32;
|
||||
let year = date.year() + years;
|
||||
|
||||
let months = value as i32 - (years * 12);
|
||||
let months = match months.cmp(&0) {
|
||||
Ordering::Less => (12 - months) as u8,
|
||||
Ordering::Equal | Ordering::Greater => months as u8,
|
||||
} + 1;
|
||||
|
||||
let date = date
|
||||
.replace_year(year)
|
||||
.ok()?
|
||||
.replace_month(Month::try_from(months).ok()?)
|
||||
.ok()?;
|
||||
Some(self.convert_datetime_to_dom_string(date))
|
||||
},
|
||||
InputType::Number | InputType::Range => {
|
||||
let mut value = DOMString::from(value.to_string());
|
||||
|
||||
value.set_best_representation_of_the_floating_point_number();
|
||||
|
||||
Ok(value)
|
||||
Some(value)
|
||||
},
|
||||
// this won't be called from other input types
|
||||
_ => unreachable!(),
|
||||
_ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#concept-input-value-string-date
|
||||
// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-date>
|
||||
// This does the safe Rust part of conversion; the unsafe JS Date part
|
||||
// is in GetValueAsDate
|
||||
fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<NaiveDateTime> {
|
||||
fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
|
||||
match self.input_type() {
|
||||
InputType::Date => value
|
||||
.parse_date_string()
|
||||
.and_then(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d))
|
||||
.and_then(|date| date.and_hms_opt(0, 0, 0)),
|
||||
InputType::Time => value.parse_time_string().and_then(|(h, m, s)| {
|
||||
let whole_seconds = s.floor();
|
||||
let nanos = ((s - whole_seconds) * 1e9).floor() as u32;
|
||||
NaiveDate::from_ymd_opt(1970, 1, 1)
|
||||
.and_then(|date| date.and_hms_nano_opt(h, m, whole_seconds as u32, nanos))
|
||||
}),
|
||||
InputType::Week => value
|
||||
.parse_week_string()
|
||||
.and_then(|(iso_year, week)| {
|
||||
NaiveDate::from_isoywd_opt(iso_year, week, Weekday::Mon)
|
||||
})
|
||||
.and_then(|date| date.and_hms_opt(0, 0, 0)),
|
||||
InputType::Month => value
|
||||
.parse_month_string()
|
||||
.and_then(|(y, m)| NaiveDate::from_ymd_opt(y, m, 1))
|
||||
.and_then(|date| date.and_hms_opt(0, 0, 0)),
|
||||
InputType::Date => value.str().parse_date_string(),
|
||||
InputType::Time => value.str().parse_time_string(),
|
||||
InputType::Week => value.str().parse_week_string(),
|
||||
InputType::Month => value.str().parse_month_string(),
|
||||
InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
|
||||
// does not apply to other types
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#concept-input-value-date-string
|
||||
// This does the safe Rust part of conversion; the unsafe JS Date part
|
||||
// is in SetValueAsDate
|
||||
fn convert_naive_datetime_to_string(&self, value: NaiveDateTime) -> Result<DOMString, ()> {
|
||||
match self.input_type() {
|
||||
InputType::Date => Ok(DOMString::from(value.format("%Y-%m-%d").to_string())),
|
||||
InputType::Month => Ok(DOMString::from(value.format("%Y-%m").to_string())),
|
||||
InputType::Week => {
|
||||
let year = value.iso_week().year(); // not necessarily the same as value.year()
|
||||
let week = value.iso_week().week();
|
||||
Ok(DOMString::from(format!("{:04}-W{:02}", year, week)))
|
||||
/// <https://html.spec.whatwg.org/multipage/#concept-input-value-date-string>
|
||||
/// This does the safe Rust part of conversion; the unsafe JS Date part
|
||||
/// is in SetValueAsDate
|
||||
fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
|
||||
DOMString::from_string(match self.input_type() {
|
||||
InputType::Date => value.to_date_string(),
|
||||
InputType::Month => value.to_month_string(),
|
||||
InputType::Week => value.to_week_string(),
|
||||
InputType::Time => value.to_time_string(),
|
||||
InputType::DatetimeLocal => value.to_local_date_time_string(),
|
||||
_ => {
|
||||
unreachable!("Should not have called convert_datetime_to_string for non-Date types")
|
||||
},
|
||||
InputType::Time => Ok(DOMString::from(value.format("%H:%M:%S%.3f").to_string())),
|
||||
// this won't be called from other input types
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2877,16 +2863,6 @@ fn round_halves_positive(n: f64) -> f64 {
|
|||
}
|
||||
}
|
||||
|
||||
fn milliseconds_to_datetime(value: f64) -> Result<NaiveDateTime, ()> {
|
||||
let seconds = (value / 1000.0).floor();
|
||||
let milliseconds = value - (seconds * 1000.0);
|
||||
let nanoseconds = milliseconds * 1e6;
|
||||
match DateTime::from_timestamp(seconds as i64, nanoseconds as u32) {
|
||||
Some(datetime) => Ok(datetime.naive_utc()),
|
||||
None => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
// This is used to compile JS-compatible regex provided in pattern attribute
|
||||
// that matches only the entirety of string.
|
||||
// https://html.spec.whatwg.org/multipage/#compiled-pattern-regular-expression
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue