diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index 83c438a26e7..9e7c01ed5ca 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -302,6 +302,20 @@ impl DOMString { pub fn is_valid_week_string(&self) -> bool { parse_week_string(&*self.0).is_ok() } + + /// A valid normalized local date and time string should be "{date}T{time}" + /// where date and time are both valid, and the time string must be as short as possible + /// https://html.spec.whatwg.org/multipage/#valid-normalised-local-date-and-time-string + pub fn convert_valid_normalized_local_date_and_time_string(&mut self) -> Result<(), ()> { + let ((year, month, day), (hour, minute, second)) = parse_local_date_and_time_string(&*self.0)?; + if second == 0.0 { + self.0 = format!("{:04}-{:02}-{:02}T{:02}:{:02}", year, month, day, hour, minute); + } else { + self.0 = format!("{:04}-{:02}-{:02}T{:02}:{:02}:{}", year, month, day, hour, minute, second); + } + Ok(()) + } + } impl Borrow for DOMString { @@ -541,6 +555,84 @@ fn parse_date_component(value: &str) -> Result<(u32, u32, u32), ()> { Ok((year_int, month_int, day_int)) } +/// https://html.spec.whatwg.org/multipage/#parse-a-time-component +fn parse_time_component(value: &str) -> Result<(u32, u32, f32), ()> { + // Step 1 + let mut iterator = value.split(':'); + let hour = iterator.next().ok_or(())?; + if hour.len() != 2 { + return Err(()); + } + let hour_int = hour.parse::().map_err(|_| ())?; + + // Step 2 + if hour_int > 23 { + return Err(()); + } + + // Step 3, 4 + let minute = iterator.next().ok_or(())?; + if minute.len() != 2 { + return Err(()); + } + let minute_int = minute.parse::().map_err(|_| ())?; + + // Step 5 + if minute_int > 59 { + return Err(()); + } + + // Step 6, 7 + let second_float = match iterator.next() { + Some(second) => { + let mut second_iterator = second.split('.'); + if second_iterator.next().ok_or(())?.len() != 2 { + return Err(()); + } + match second_iterator.next() { + Some(second_last) => { + if second_last.len() > 3 { + return Err(()); + } + }, + None => {} + } + + second.parse::().map_err(|_| ())? + }, + None => 0.0 + }; + + // Step 8 + Ok((hour_int, minute_int, second_float)) +} + +// https://html.spec.whatwg.org/multipage/#parse-a-local-date-and-time-string +fn parse_local_date_and_time_string(value: &str) -> Result<((u32, u32, u32), (u32, u32, f32)), ()> { + // Step 1, 2, 4 + let mut iterator = if value.contains('T') { + value.split('T') + } else { + value.split(' ') + }; + + // Step 3 + let date = iterator.next().ok_or(())?; + let date_tuple = parse_date_component(date)?; + + // Step 5 + let time = iterator.next().ok_or(())?; + let time_tuple = parse_time_component(time)?; + + // Step 6 + if iterator.next().is_some() { + return Err(()); + } + + // Step 7, 8, 9 + Ok((date_tuple, time_tuple)) +} + fn max_day_in_month(year_num: u32, month_num: u32) -> Result { match month_num { 1|3|5|7|8|10|12 => Ok(31), diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 8c5699446ef..defbc1f4b6f 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -996,19 +996,19 @@ impl HTMLInputElement { InputType::Date => { let mut textinput = self.textinput.borrow_mut(); if !textinput.single_line_content().is_valid_date_string() { - *textinput.single_line_content_mut() = "".into(); + textinput.single_line_content_mut().clear(); } } InputType::Month => { let mut textinput = self.textinput.borrow_mut(); if !textinput.single_line_content().is_valid_month_string() { - *textinput.single_line_content_mut() = "".into(); + textinput.single_line_content_mut().clear(); } } InputType::Week => { let mut textinput = self.textinput.borrow_mut(); if !textinput.single_line_content().is_valid_week_string() { - *textinput.single_line_content_mut() = "".into(); + textinput.single_line_content_mut().clear(); } } InputType::Color => { @@ -1034,8 +1034,15 @@ impl HTMLInputElement { InputType::Time => { let mut textinput = self.textinput.borrow_mut(); - if ! textinput.single_line_content().is_valid_time_string() { - *textinput.single_line_content_mut() = "".into(); + if !textinput.single_line_content().is_valid_time_string() { + textinput.single_line_content_mut().clear(); + } + } + InputType::DatetimeLocal => { + let mut textinput = self.textinput.borrow_mut(); + if textinput.single_line_content_mut() + .convert_valid_normalized_local_date_and_time_string().is_err() { + textinput.single_line_content_mut().clear(); } } // TODO: Implement more value sanitization algorithms for different types of inputs diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 30613dca959..8b7f5db73cf 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -543003,7 +543003,7 @@ "testharness" ], "html/semantics/forms/the-input-element/datetime-local.html": [ - "7fe3ab2bd5a8d60e11d08460286ee8f02943b769", + "382370387908b4625474b410621497a76476a0cf", "testharness" ], "html/semantics/forms/the-input-element/datetime.html": [ diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/datetime-local.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/datetime-local.html.ini index 7dfda156297..ed93cfbd3d9 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/datetime-local.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-input-element/datetime-local.html.ini @@ -1,29 +1,9 @@ [datetime-local.html] type: testharness - [datetime-local input value set to 2014-01-01 11:11:11.111 without min/max] - expected: FAIL - - [datetime-local input value set to 2014-01-01 11:11 without min/max] - expected: FAIL - - [datetime-local input value set to 2014-01-01 00:00:00.000 without min/max] - expected: FAIL - - [datetime-local input value set to 2014-01-0 11:11 without min/max] - expected: FAIL - - [datetime-local input value set to 2014-01-01 11:1 without min/max] - expected: FAIL - - [Value >= min attribute] - expected: FAIL [Value < min attribute] expected: FAIL - [Value <= max attribute] - expected: FAIL - [Value > max attribute] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini index 4de9d689a17..da29acb8975 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini @@ -225,9 +225,6 @@ [change state from checkbox to email] expected: FAIL - [change state from checkbox to datetime] - expected: FAIL - [change state from checkbox to number] expected: FAIL @@ -294,96 +291,9 @@ [change state from button to range] expected: FAIL - [change state from hidden to datetime-local] - expected: FAIL - - [change state from text to datetime-local] - expected: FAIL - - [change state from search to datetime-local] - expected: FAIL - - [change state from tel to datetime-local] - expected: FAIL - - [change state from url to datetime-local] - expected: FAIL - - [change state from email to datetime-local] - expected: FAIL - - [change state from password to datetime-local] - expected: FAIL - - [change state from datetime-local to hidden] - expected: FAIL - - [change state from datetime-local to checkbox] - expected: FAIL - - [change state from datetime-local to radio] - expected: FAIL - - [change state from datetime-local to submit] - expected: FAIL - - [change state from datetime-local to image] - expected: FAIL - - [change state from datetime-local to reset] - expected: FAIL - - [change state from datetime-local to button] - expected: FAIL - - [change state from datetime-local to email] - expected: FAIL - - [change state from datetime-local to number] - expected: FAIL - [change state from datetime-local to range] expected: FAIL - [change state from number to datetime-local] - expected: FAIL - - [change state from range to datetime-local] - expected: FAIL - - [change state from checkbox to datetime-local] - expected: FAIL - - [change state from radio to datetime-local] - expected: FAIL - - [change state from submit to datetime-local] - expected: FAIL - - [change state from image to datetime-local] - expected: FAIL - - [change state from reset to datetime-local] - expected: FAIL - - [change state from button to datetime-local] - expected: FAIL - - [change state from datetime-local to text] - expected: FAIL - - [change state from datetime-local to search] - expected: FAIL - - [change state from datetime-local to tel] - expected: FAIL - - [change state from datetime-local to url] - expected: FAIL - - [change state from datetime-local to password] - expected: FAIL - [change state from number to text] expected: FAIL @@ -414,9 +324,6 @@ [change state from range to password] expected: FAIL - [change state from color to datetime-local] - expected: FAIL - [change state from color to number] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/valueMode.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/valueMode.html.ini index b794bb8fc75..8d0043a2322 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/valueMode.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-input-element/valueMode.html.ini @@ -18,12 +18,6 @@ [value IDL attribute of input type range with value attribute] expected: FAIL - [value IDL attribute of input type datetime-local without value attribute] - expected: FAIL - - [value IDL attribute of input type datetime-local with value attribute] - expected: FAIL - [value IDL attribute of input type email without value attribute] expected: FAIL diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/datetime-local.html b/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/datetime-local.html index b4548b77aac..60a68170de4 100644 --- a/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/datetime-local.html +++ b/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/datetime-local.html @@ -16,6 +16,12 @@ {value: "2014-01-01 00:00:00.000", expected: "2014-01-01T00:00", testname: "datetime-local input value set to 2014-01-01 00:00:00.000 without min/max"}, {value: "2014-01-0 11:11", expected: "", testname: "datetime-local input value set to 2014-01-0 11:11 without min/max"}, {value: "2014-01-01 11:1", expected: "", testname: "datetime-local input value set to 2014-01-01 11:1 without min/max"}, + {value: "2014-01-01 11:1d1", expected: "", testname: "invalid datetime-local input value 1"}, + {value: "2014-01-01H11:11", expected: "", testname: "invalid datetime-local input value 2"}, + {value: "2014-01-01 11:11:", expected: "", testname: "invalid datetime-local input value 3"}, + {value: "2014-01-01 11-11", expected: "", testname: "invalid datetime-local input value 4"}, + {value: "2014-01-01 11:11:123", expected: "", testname: "invalid datetime-local input value 5"}, + {value: "2014-01-01 11:11:12.1234", expected: "", testname: "invalid datetime-local input value 6"}, {value: "2014-01-01 11:12", attributes: { min: "2014-01-01 11:11" }, expected: "2014-01-01T11:12", testname: "Value >= min attribute"}, {value: "2014-01-01 11:10", attributes: { min: "2014-01-01 11:11" }, expected: "2014-01-01T11:11", testname: "Value < min attribute"}, {value: "2014-01-01 11:10", attributes: { max: "2014-01-01 11:11" }, expected: "2014-01-01T11:10", testname: "Value <= max attribute"},