diff --git a/Cargo.lock b/Cargo.lock index 3f83b82fe00..fee2ff7976f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,6 +358,15 @@ dependencies = [ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "chrono" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clang-sys" version = "0.21.1" @@ -2044,6 +2053,16 @@ dependencies = [ "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.34" @@ -2538,6 +2557,7 @@ dependencies = [ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "canvas_traits 0.0.1", "caseless 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "cmake 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "cssparser 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3776,6 +3796,7 @@ dependencies = [ "checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum cgl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86765cb42c2a2c497e142af72517c1b4d7ae5bb2f25dfa77a5c69642f2342d89" +"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" "checksum clang-sys 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00048189ee171715296dfe3b2fcfd439563c7bfec0d98d3976ce3402d62c8f07" "checksum clap 2.28.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc34bf7d5d66268b466b9852bca925ec1d2650654dab4da081e63fd230145c2e" "checksum clipboard 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd3a9a938558f33ec1baaa6ca631a69c104aafaacbc66868d9ad28cf5f30564f" @@ -3895,6 +3916,7 @@ dependencies = [ "checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" +"checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" "checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" "checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" "checksum num-rational 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "c2dc5ea04020a8f18318ae485c751f8cfa1c0e69dcf465c29ddaaa64a313cc44" diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 5561cd7dd95..8c3c77ed27c 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -39,6 +39,7 @@ byteorder = "1.0" canvas_traits = {path = "../canvas_traits"} caseless = "0.1.0" cookie = "0.10" +chrono = "0.4" cssparser = "0.23.0" deny_public_fields = {path = "../deny_public_fields"} devtools_traits = {path = "../devtools_traits"} diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index 596b2db14b2..83c438a26e7 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -4,6 +4,8 @@ //! The `ByteString` struct. +use chrono::{Datelike, TimeZone}; +use chrono::prelude::{Weekday, Utc}; use cssparser::CowRcStr; use html5ever::{LocalName, Namespace}; use servo_atoms::Atom; @@ -293,6 +295,13 @@ impl DOMString { pub fn is_valid_month_string(&self) -> bool { parse_month_string(&*self.0).is_ok() } + + /// A valid week string should be like {YYYY}-W{WW}, such as "2017-W52" + /// YYYY must be four or more digits, WW both must be two digits + /// https://html.spec.whatwg.org/multipage/#valid-week-string + pub fn is_valid_week_string(&self) -> bool { + parse_week_string(&*self.0).is_ok() + } } impl Borrow for DOMString { @@ -445,6 +454,48 @@ fn parse_date_string(value: &str) -> Result<(u32, u32, u32), ()> { Ok((year_int, month_int, day_int)) } +/// https://html.spec.whatwg.org/multipage/#parse-a-week-string +fn parse_week_string(value: &str) -> Result<(u32, u32), ()> { + // Step 1, 2, 3 + let mut iterator = value.split('-'); + let year = iterator.next().ok_or(())?; + + // Step 4 + let year_int = year.parse::().map_err(|_| ())?; + if year.len() < 4 || year_int == 0 { + return Err(()); + } + + // Step 5, 6 + let week = iterator.next().ok_or(())?; + let (week_first, week_last) = week.split_at(1); + if week_first != "W" { + return Err(()); + } + + // Step 7 + let week_int = week_last.parse::().map_err(|_| ())?; + if week_last.len() != 2 { + return Err(()); + } + + // Step 8 + let max_week = max_week_in_year(year_int); + + // Step 9 + if week_int < 1 || week_int > max_week { + return Err(()); + } + + // Step 10 + if iterator.next().is_some() { + return Err(()); + } + + // Step 11 + Ok((year_int, week_int)) +} + /// https://html.spec.whatwg.org/multipage/#parse-a-month-component fn parse_month_component(value: &str) -> Result<(u32, u32), ()> { // Step 3 @@ -495,7 +546,7 @@ fn max_day_in_month(year_num: u32, month_num: u32) -> Result { 1|3|5|7|8|10|12 => Ok(31), 4|6|9|11 => Ok(30), 2 => { - if year_num % 400 == 0 || (year_num % 4 == 0 && year_num % 100 != 0) { + if is_leap_year(year_num) { Ok(29) } else { Ok(28) @@ -504,3 +555,17 @@ fn max_day_in_month(year_num: u32, month_num: u32) -> Result { _ => Err(()) } } + +// https://html.spec.whatwg.org/multipage/#week-number-of-the-last-day +fn max_week_in_year(year: u32) -> u32 { + match Utc.ymd(year as i32, 1, 1).weekday() { + Weekday::Thu => 53, + Weekday::Wed if is_leap_year(year) => 53, + _ => 52 + } +} + +#[inline] +fn is_leap_year(year: u32) -> bool { + year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) +} diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index a951c49f80c..8c5699446ef 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -1005,6 +1005,12 @@ impl HTMLInputElement { *textinput.single_line_content_mut() = "".into(); } } + InputType::Week => { + let mut textinput = self.textinput.borrow_mut(); + if !textinput.single_line_content().is_valid_week_string() { + *textinput.single_line_content_mut() = "".into(); + } + } InputType::Color => { let mut textinput = self.textinput.borrow_mut(); diff --git a/components/script/lib.rs b/components/script/lib.rs index 3bae98a1a06..3143342e4f6 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -31,6 +31,7 @@ extern crate bluetooth_traits; extern crate byteorder; extern crate canvas_traits; extern crate caseless; +extern crate chrono; extern crate cookie as cookie_rs; #[macro_use] extern crate cssparser; #[macro_use] extern crate deny_public_fields; diff --git a/servo-tidy.toml b/servo-tidy.toml index 6f96592eafa..d9bcc9def68 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -29,7 +29,9 @@ rand = [ "uuid", "ws", ] -num = [] +num = [ + "chrono", +] [ignore] # Ignored packages with duplicated versions diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 35c55c9a317..8358cb86834 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -543087,7 +543087,7 @@ "testharness" ], "html/semantics/forms/the-input-element/week.html": [ - "851b1b794f820b1fb9b7ee57fe39f8f2977b7fe6", + "95fa0e8176311adcbb5a8d0d408d3c4c8bea100c", "testharness" ], "html/semantics/forms/the-label-element/.gitkeep": [ 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 185dd921114..4de9d689a17 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 @@ -6,9 +6,6 @@ [change state from hidden to datetime] expected: FAIL - [change state from hidden to week] - expected: FAIL - [change state from hidden to number] expected: FAIL @@ -21,9 +18,6 @@ [change state from text to datetime] expected: FAIL - [change state from text to week] - expected: FAIL - [change state from text to number] expected: FAIL @@ -36,9 +30,6 @@ [change state from search to datetime] expected: FAIL - [change state from search to week] - expected: FAIL - [change state from search to number] expected: FAIL @@ -51,9 +42,6 @@ [change state from tel to datetime] expected: FAIL - [change state from tel to week] - expected: FAIL - [change state from tel to number] expected: FAIL @@ -75,9 +63,6 @@ [change state from url to datetime] expected: FAIL - [change state from url to week] - expected: FAIL - [change state from url to number] expected: FAIL @@ -108,9 +93,6 @@ [change state from email to datetime] expected: FAIL - [change state from email to week] - expected: FAIL - [change state from email to number] expected: FAIL @@ -123,9 +105,6 @@ [change state from password to datetime] expected: FAIL - [change state from password to week] - expected: FAIL - [change state from password to number] expected: FAIL @@ -171,27 +150,9 @@ [change state from month to range] expected: FAIL - [change state from week to reset] - expected: FAIL - - [change state from week to button] - expected: FAIL - - [change state from week to email] - expected: FAIL - [change state from week to datetime] expected: FAIL - [change state from week to number] - expected: FAIL - - [change state from week to hidden] - expected: FAIL - - [change state from week to number] - expected: FAIL - [change state from week to range] expected: FAIL @@ -228,9 +189,6 @@ [change state from number to datetime] expected: FAIL - [change state from number to week] - expected: FAIL - [change state from number to range] expected: FAIL @@ -261,9 +219,6 @@ [change state from range to datetime] expected: FAIL - [change state from range to week] - expected: FAIL - [change state from range to number] expected: FAIL @@ -273,9 +228,6 @@ [change state from checkbox to datetime] expected: FAIL - [change state from checkbox to week] - expected: FAIL - [change state from checkbox to number] expected: FAIL @@ -288,9 +240,6 @@ [change state from radio to datetime] expected: FAIL - [change state from radio to week] - expected: FAIL - [change state from radio to number] expected: FAIL @@ -303,9 +252,6 @@ [change state from submit to datetime] expected: FAIL - [change state from submit to week] - expected: FAIL - [change state from submit to number] expected: FAIL @@ -318,9 +264,6 @@ [change state from image to datetime] expected: FAIL - [change state from image to week] - expected: FAIL - [change state from image to number] expected: FAIL @@ -333,9 +276,6 @@ [change state from reset to datetime] expected: FAIL - [change state from reset to week] - expected: FAIL - [change state from reset to number] expected: FAIL @@ -348,9 +288,6 @@ [change state from button to datetime] expected: FAIL - [change state from button to week] - expected: FAIL - [change state from button to number] expected: FAIL @@ -402,18 +339,12 @@ [change state from datetime-local to email] expected: FAIL - [change state from datetime-local to week] - expected: FAIL - [change state from datetime-local to number] expected: FAIL [change state from datetime-local to range] expected: FAIL - [change state from week to datetime-local] - expected: FAIL - [change state from number to datetime-local] expected: FAIL @@ -453,33 +384,6 @@ [change state from datetime-local to password] expected: FAIL - [change state from week to text] - expected: FAIL - - [change state from week to search] - expected: FAIL - - [change state from week to tel] - expected: FAIL - - [change state from week to url] - expected: FAIL - - [change state from week to password] - expected: FAIL - - [change state from week to checkbox] - expected: FAIL - - [change state from week to radio] - expected: FAIL - - [change state from week to submit] - expected: FAIL - - [change state from week to image] - expected: FAIL - [change state from number to text] expected: FAIL @@ -513,9 +417,6 @@ [change state from color to datetime-local] expected: FAIL - [change state from color to week] - 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 4dd99f109cd..b794bb8fc75 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 @@ -6,12 +6,6 @@ [value IDL attribute of input type datetime with value attribute] expected: FAIL - [value IDL attribute of input type week without value attribute] - expected: FAIL - - [value IDL attribute of input type week with value attribute] - expected: FAIL - [value IDL attribute of input type number without value attribute] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/week.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/week.html.ini deleted file mode 100644 index 3c94a479263..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/week.html.ini +++ /dev/null @@ -1,20 +0,0 @@ -[week.html] - type: testharness - [2014 has 52 weeks: Value should be empty] - expected: FAIL - - [Invalid value: year only] - expected: FAIL - - [Invalid value: no week number] - expected: FAIL - - [Invalid value: no '-' (U+002D)] - expected: FAIL - - [Invalid value: yearless week] - expected: FAIL - - [Invalid value: yearless week and no '-' (U+002D)] - expected: FAIL - diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/week.html b/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/week.html index 77978a255a3..925acfdaf8b 100644 --- a/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/week.html +++ b/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/week.html @@ -17,6 +17,9 @@ {value: "2014W", expected: "", testname: "Invalid value: no week number"}, {value: "2014W52", expected: "", testname: "Invalid value: no '-' (U+002D)"}, {value: "-W52", expected: "", testname: "Invalid value: yearless week"}, + {value: "2017-w52", expected: "", testname: "Invalid value: should be capital letter 'W'"}, + {value: "2017-W52-", expected: "", testname: "Invalid value: incorrect with '-' at the end"}, + {value: "2017-W52-12", expected: "", testname: "Invalid value: value should be two parts"}, {value: "W52", expected: "", testname: "Invalid value: yearless week and no '-' (U+002D)"}, {value: "2014-W03", attributes: { min: "2014-W02" }, expected: "2014-W03", testname: "Value >= min attribute"}, {value: "2014-W01", attributes: { min: "2014-W02" }, expected: "2014-W01", testname: "Value < min attribute"},