diff options
-rw-r--r-- | dom/html/HTMLInputElement.cpp | 112 | ||||
-rw-r--r-- | dom/html/HTMLInputElement.h | 14 | ||||
-rw-r--r-- | dom/html/test/forms/test_valueasdate_attribute.html | 108 | ||||
-rw-r--r-- | dom/html/test/forms/test_valueasnumber_attribute.html | 123 | ||||
-rw-r--r-- | js/public/Date.h | 8 | ||||
-rwxr-xr-x | js/src/jsdate.cpp | 12 |
6 files changed, 351 insertions, 26 deletions
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 62f194493..b9efa0066 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -1918,6 +1918,22 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue, aResultValue = Decimal::fromDouble(days * kMsPerDay); return true; } + case NS_FORM_INPUT_DATETIME_LOCAL: + { + uint32_t year, month, day, timeInMs; + if (!ParseDateTimeLocal(aValue, &year, &month, &day, &timeInMs)) { + return false; + } + + JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day, + timeInMs)); + if (!time.isValid()) { + return false; + } + + aResultValue = Decimal::fromDouble(time.toDouble()); + return true; + } default: MOZ_ASSERT(false, "Unrecognized input type"); return false; @@ -2107,21 +2123,17 @@ HTMLInputElement::ConvertNumberToString(Decimal aValue, } case NS_FORM_INPUT_TIME: { + aValue = aValue.floor(); // Per spec, we need to truncate |aValue| and we should only represent // times inside a day [00:00, 24:00[, which means that we should do a // modulo on |aValue| using the number of milliseconds in a day (86400000). - uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble(); - - uint16_t milliseconds = value % 1000; - value /= 1000; - - uint8_t seconds = value % 60; - value /= 60; - - uint8_t minutes = value % 60; - value /= 60; + uint32_t value = + NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble(); - uint8_t hours = value; + uint16_t milliseconds, seconds, minutes, hours; + if (!GetTimeFromMs(value, &hours, &minutes, &seconds, &milliseconds)) { + return false; + } if (milliseconds != 0) { aResultString.AppendPrintf("%02d:%02d:%02d.%03d", @@ -2191,6 +2203,42 @@ HTMLInputElement::ConvertNumberToString(Decimal aValue, aResultString.AppendPrintf("%04.0f-W%02d", year, week); return true; } + case NS_FORM_INPUT_DATETIME_LOCAL: + { + aValue = aValue.floor(); + + uint32_t timeValue = + NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble(); + + uint16_t milliseconds, seconds, minutes, hours; + if (!GetTimeFromMs(timeValue, + &hours, &minutes, &seconds, &milliseconds)) { + return false; + } + + double year = JS::YearFromTime(aValue.toDouble()); + double month = JS::MonthFromTime(aValue.toDouble()); + double day = JS::DayFromTime(aValue.toDouble()); + + if (IsNaN(year) || IsNaN(month) || IsNaN(day)) { + return false; + } + + if (milliseconds != 0) { + aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d.%03d", + year, month + 1, day, hours, minutes, + seconds, milliseconds); + } else if (seconds != 0) { + aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d", + year, month + 1, day, hours, minutes, + seconds); + } else { + aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d", + year, month + 1, day, hours, minutes); + } + + return true; + } default: MOZ_ASSERT(false, "Unrecognized input type"); return false; @@ -2201,8 +2249,7 @@ HTMLInputElement::ConvertNumberToString(Decimal aValue, Nullable<Date> HTMLInputElement::GetValueAsDate(ErrorResult& aRv) { - // TODO: this is temporary until bug 888331 is fixed. - if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) { + if (!IsDateTimeInputType(mType)) { return Nullable<Date>(); } @@ -2260,6 +2307,19 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv) return Nullable<Date>(Date(time)); } + case NS_FORM_INPUT_DATETIME_LOCAL: + { + uint32_t year, month, day, timeInMs; + nsAutoString value; + GetNonFileValueInternal(value); + if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) { + return Nullable<Date>(); + } + + JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day, + timeInMs)); + return Nullable<Date>(Date(time)); + } } MOZ_ASSERT(false, "Unrecognized input type"); @@ -2270,8 +2330,7 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv) void HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv) { - // TODO: this is temporary until bug 888331 is fixed. - if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) { + if (!IsDateTimeInputType(mType)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } @@ -5385,6 +5444,29 @@ HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const } bool +HTMLInputElement::GetTimeFromMs(double aValue, uint16_t* aHours, + uint16_t* aMinutes, uint16_t* aSeconds, + uint16_t* aMilliseconds) const { + MOZ_ASSERT(aValue >= 0 && aValue < kMsPerDay, + "aValue must be milliseconds within a day!"); + + uint32_t value = floor(aValue); + + *aMilliseconds = value % 1000; + value /= 1000; + + *aSeconds = value % 60; + value /= 60; + + *aMinutes = value % 60; + value /= 60; + + *aHours = value; + + return true; +} + +bool HTMLInputElement::IsValidWeek(const nsAString& aValue) const { uint32_t year, week; diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 9baaffb63..56f40a60a 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -1075,11 +1075,7 @@ protected: /** * Returns if valueAsNumber attribute applies for the current type. */ - bool DoesValueAsNumberApply() const - { - // TODO: this is temporary until bug 888331 is fixed. - return DoesMinMaxApply() && mType != NS_FORM_INPUT_DATETIME_LOCAL; - } + bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); } /** * Returns if autocomplete attribute applies for the current type. @@ -1287,6 +1283,7 @@ protected: * https://html.spec.whatwg.org/multipage/infrastructure.html#valid-normalised-local-date-and-time-string */ void NormalizeDateTimeLocal(nsAString& aValue) const; + /** * This methods returns the number of days since epoch for a given year and * week. @@ -1318,6 +1315,13 @@ protected: uint32_t MaximumWeekInYear(uint32_t aYear) const; /** + * This method converts aValue (milliseconds within a day) to hours, minutes, + * seconds and milliseconds. + */ + bool GetTimeFromMs(double aValue, uint16_t* aHours, uint16_t* aMinutes, + uint16_t* aSeconds, uint16_t* aMilliseconds) const; + + /** * This methods returns true if it's a leap year. */ bool IsLeapYear(uint32_t aYear) const; diff --git a/dom/html/test/forms/test_valueasdate_attribute.html b/dom/html/test/forms/test_valueasdate_attribute.html index 8c19fefd9..65cab3b8e 100644 --- a/dom/html/test/forms/test_valueasdate_attribute.html +++ b/dom/html/test/forms/test_valueasdate_attribute.html @@ -47,8 +47,7 @@ var validTypes = ["color", false], ["month", true], ["week", true], - // TODO: temporary set to false until bug 888331 is fixed. - ["datetime-local", false], + ["datetime-local", true], ]; function checkAvailability() @@ -622,6 +621,107 @@ function checkWeekSet() } } +function checkDatetimeLocalGet() +{ + var validData = + [ + // Simple cases. + [ "2016-12-27T10:30", Date.UTC(2016, 11, 27, 10, 30, 0) ], + [ "2016-12-27T10:30:40", Date.UTC(2016, 11, 27, 10, 30, 40) ], + [ "2016-12-27T10:30:40.567", Date.UTC(2016, 11, 27, 10, 30, 40, 567) ], + [ "1969-12-31T12:00:00", Date.UTC(1969, 11, 31, 12, 0, 0) ], + [ "1970-01-01T00:00", 0 ], + // Leap years. + [ "1804-02-29 12:34", Date.UTC(1804, 1, 29, 12, 34, 0) ], + [ "2016-02-29T12:34", Date.UTC(2016, 1, 29, 12, 34, 0) ], + [ "2016-12-31T12:34:56", Date.UTC(2016, 11, 31, 12, 34, 56) ], + [ "2016-01-01T12:34:56.789", Date.UTC(2016, 0, 1, 12, 34, 56, 789) ], + [ "2017-01-01 12:34:56.789", Date.UTC(2017, 0, 1, 12, 34, 56, 789) ], + // Maximum valid datetime-local (limited by the ecma date object range). + [ "275760-09-13T00:00", 8640000000000000 ], + // Minimum valid datetime-local (limited by the input element minimum valid value). + [ "0001-01-01T00:00", -62135596800000 ], + ]; + + var invalidData = + [ + [ "invaliddateime-local" ], + [ "0000-01-01T00:00" ], + [ "2016-12-25T00:00Z" ], + [ "2015-02-29T12:34" ], + [ "1-1-1T12:00" ], + [ "" ], + // This datetime-local is valid for the input element, but is out of the + // date object range. In this case, on getting valueAsDate, a Date object + // will be created, but it will have a NaN internal value, and will return + // the string "Invalid Date". + [ "275760-09-13T12:00", true ], + ]; + + element.type = "datetime-local"; + for (let data of validData) { + element.value = data[0]; + is(element.valueAsDate.valueOf(), data[1], + "valueAsDate should return the " + + "valid date object representing this datetime-local"); + } + + for (let data of invalidData) { + element.value = data[0]; + if (data[1]) { + is(String(element.valueAsDate), "Invalid Date", + "valueAsDate should return an invalid Date object " + + "when the element value is not a valid datetime-local"); + } else { + is(element.valueAsDate, null, + "valueAsDate should return null " + + "when the element value is not a valid datetime-local"); + } + } +} + +function checkDatetimeLocalSet() +{ + var testData = + [ + // Simple cases. + [ Date.UTC(2016, 11, 27, 10, 30, 0), "2016-12-27T10:30" ], + [ Date.UTC(2016, 11, 27, 10, 30, 30), "2016-12-27T10:30:30" ], + [ Date.UTC(1999, 11, 31, 23, 59, 59), "1999-12-31T23:59:59" ], + [ Date.UTC(1999, 11, 31, 23, 59, 59, 999), "1999-12-31T23:59:59.999" ], + [ Date.UTC(123456, 7, 8, 9, 10), "123456-08-08T09:10" ], + [ 0, "1970-01-01T00:00" ], + // Maximum valid datetime-local (limited by the ecma date object range). + [ 8640000000000000, "275760-09-13T00:00" ], + // Minimum valid datetime-local (limited by the input element minimum valid value). + [ -62135596800000, "0001-01-01T00:00" ], + // Leap years. + [ Date.UTC(1804, 1, 29, 12, 34, 0), "1804-02-29T12:34" ], + [ Date.UTC(2016, 1, 29, 12, 34, 0), "2016-02-29T12:34" ], + [ Date.UTC(2016, 11, 31, 12, 34, 56), "2016-12-31T12:34:56" ], + [ Date.UTC(2016, 0, 1, 12, 34, 56, 789), "2016-01-01T12:34:56.789" ], + [ Date.UTC(2017, 0, 1, 12, 34, 56, 789), "2017-01-01T12:34:56.789" ], + // "Values must be truncated to valid datetime-local" + [ 123.123456789123, "1970-01-01T00:00:00.123" ], + [ 1e-1, "1970-01-01T00:00" ], + [ -1.1, "1969-12-31T23:59:59.999" ], + [ -345600000, "1969-12-28T00:00" ], + // Negative years, this is out of range for the input element, + // the corresponding datetime-local string is the empty string + [ -62135596800001, "" ], + ]; + + element.type = "datetime-local"; + for (let data of testData) { + element.valueAsDate = new Date(data[0]); + is(element.value, data[1], "valueAsDate should set the value to " + + data[1]); + element.valueAsDate = new testFrame.Date(data[0]); + is(element.value, data[1], "valueAsDate with other-global date should " + + "set the value to " + data[1]); + } +} + checkAvailability(); checkGarbageValues(); checkWithBustedPrototype(); @@ -642,6 +742,10 @@ checkMonthSet(); checkWeekGet(); checkWeekSet(); +// Test <input type='datetime-local'>. +checkDatetimeLocalGet(); +checkDatetimeLocalSet(); + </script> </pre> </body> diff --git a/dom/html/test/forms/test_valueasnumber_attribute.html b/dom/html/test/forms/test_valueasnumber_attribute.html index d7471502b..2660fc7ed 100644 --- a/dom/html/test/forms/test_valueasnumber_attribute.html +++ b/dom/html/test/forms/test_valueasnumber_attribute.html @@ -46,8 +46,7 @@ function checkAvailability() ["color", false], ["month", true], ["week", true], - // TODO: temporary set to false until bug 888331 is fixed. - ["datetime-local", false], + ["datetime-local", true], ]; var element = document.createElement('input'); @@ -612,7 +611,6 @@ function checkWeekGet() var element = document.createElement('input'); element.type = "week"; for (let data of validData) { - dump("Test: " + data[0]); element.value = data[0]; is(element.valueAsNumber, data[1], "valueAsNumber should return the " + "integer value representing this week"); @@ -698,7 +696,120 @@ function checkWeekSet() try { element.valueAsNumber = data[0]; - is(element.value, data[1], "valueAsNumber should set the value to " + data[1]); + is(element.value, data[1], "valueAsNumber should set the value to " + + data[1]); + } catch(e) { + caught = true; + } + + if (data[2]) { + ok(caught, "valueAsNumber should have thrown"); + is(element.value, data[1], "the value should not have changed"); + } else { + ok(!caught, "valueAsNumber should not have thrown"); + } + } +} + +function checkDatetimeLocalGet() { + var validData = + [ + // Simple cases. + [ "2016-12-20T09:58", Date.UTC(2016, 11, 20, 9, 58) ], + [ "2016-12-20T09:58:30", Date.UTC(2016, 11, 20, 9, 58, 30) ], + [ "2016-12-20T09:58:30.123", Date.UTC(2016, 11, 20, 9, 58, 30, 123) ], + [ "2017-01-01T10:00", Date.UTC(2017, 0, 1, 10, 0, 0) ], + [ "1969-12-31T12:00:00", Date.UTC(1969, 11, 31, 12, 0, 0) ], + [ "1970-01-01T00:00", 0 ], + // Leap years. + [ "1804-02-29 12:34", Date.UTC(1804, 1, 29, 12, 34, 0) ], + [ "2016-02-29T12:34", Date.UTC(2016, 1, 29, 12, 34, 0) ], + [ "2016-12-31T12:34:56", Date.UTC(2016, 11, 31, 12, 34, 56) ], + [ "2016-01-01T12:34:56.789", Date.UTC(2016, 0, 1, 12, 34, 56, 789) ], + [ "2017-01-01 12:34:56.789", Date.UTC(2017, 0, 1, 12, 34, 56, 789) ], + // Maximum valid datetime-local (limited by the ecma date object range). + [ "275760-09-13T00:00", 8640000000000000 ], + // Minimum valid datetime-local (limited by the input element minimum valid value). + [ "0001-01-01T00:00", -62135596800000 ], + ]; + + var invalidData = + [ + "invaliddatetime-local", + "0000-01-01T00:00", + "2016-12-25T00:00Z", + "2015-02-29T12:34", + "1-1-1T12:00", + // Out of range. + "275760-09-13T12:00", + ]; + + var element = document.createElement('input'); + element.type = "datetime-local"; + for (let data of validData) { + element.value = data[0]; + is(element.valueAsNumber, data[1], "valueAsNumber should return the " + + "integer value representing this datetime-local"); + } + + for (let data of invalidData) { + element.value = data; + ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " + + "when the element value is not a valid datetime-local"); + } +} + +function checkDatetimeLocalSet() +{ + var testData = + [ + // Simple cases. + [ Date.UTC(2016, 11, 20, 9, 58, 0), "2016-12-20T09:58", ], + [ Date.UTC(2016, 11, 20, 9, 58, 30), "2016-12-20T09:58:30" ], + [ Date.UTC(2016, 11, 20, 9, 58, 30, 123), "2016-12-20T09:58:30.123" ], + [ Date.UTC(2017, 0, 1, 10, 0, 0), "2017-01-01T10:00" ], + [ Date.UTC(1969, 11, 31, 12, 0, 0), "1969-12-31T12:00" ], + [ 0, "1970-01-01T00:00" ], + // Maximum valid week (limited by the ecma date object range). + [ 8640000000000000, "275760-09-13T00:00" ], + // Minimum valid datetime-local (limited by the input element minimum valid value). + [ -62135596800000, "0001-01-01T00:00" ], + // Leap years. + [ Date.UTC(1804, 1, 29, 12, 34, 0), "1804-02-29T12:34" ], + [ Date.UTC(2016, 1, 29, 12, 34, 0), "2016-02-29T12:34" ], + [ Date.UTC(2016, 11, 31, 12, 34, 56), "2016-12-31T12:34:56" ], + [ Date.UTC(2016, 0, 1, 12, 34, 56, 789), "2016-01-01T12:34:56.789" ], + [ Date.UTC(2017, 0, 1, 12, 34, 56, 789), "2017-01-01T12:34:56.789" ], + // "Values must be truncated to valid datetime-local" + [ 0.3, "1970-01-01T00:00" ], + [ 1e-1, "1970-01-01T00:00" ], + [ -1 , "1969-12-31T23:59:59.999" ], + [ -345600000, "1969-12-28T00:00" ], + // Invalid numbers. + // Those are implicitly converted to numbers + [ "", "1970-01-01T00:00" ], + [ true, "1970-01-01T00:00:00.001" ], + [ false, "1970-01-01T00:00" ], + [ null, "1970-01-01T00:00" ], + // Those are converted to NaN, the corresponding week string is the empty string + [ "invaliddatetime-local", "" ], + [ NaN, "" ], + [ undefined, "" ], + // Infinity will keep the current value and throw (so we need to set a current value). + [ Date.UTC(2016, 11, 27, 15, 10, 0), "2016-12-27T15:10" ], + [ Infinity, "2016-12-27T15:10", true ], + [ -Infinity, "2016-12-27T15:10", true ], + ]; + + var element = document.createElement('input'); + element.type = "datetime-local"; + for (let data of testData) { + var caught = false; + + try { + element.valueAsNumber = data[0]; + is(element.value, data[1], "valueAsNumber should set the value to " + + data[1]); } catch(e) { caught = true; } @@ -738,6 +849,10 @@ checkMonthSet(); checkWeekGet(); checkWeekSet(); +// <input type='datetime-local'> test +checkDatetimeLocalGet(); +checkDatetimeLocalSet(); + </script> </pre> </body> diff --git a/js/public/Date.h b/js/public/Date.h index cba0ea875..cab36a3de 100644 --- a/js/public/Date.h +++ b/js/public/Date.h @@ -134,6 +134,14 @@ NewDateObject(JSContext* cx, ClippedTime time); JS_PUBLIC_API(double) MakeDate(double year, unsigned month, unsigned day); +// Year is a year, month is 0-11, day is 1-based, and time is in milliseconds. +// The return value is a number of milliseconds since the epoch. +// +// Consistent with the MakeDate algorithm defined in ECMAScript, this value is +// *not* clipped! Use JS::TimeClip if you need a clipped date. +JS_PUBLIC_API(double) +MakeDate(double year, unsigned month, unsigned day, double time); + // Takes an integer number of milliseconds since the epoch and returns the // year. Can return NaN, and will do so if NaN is passed in. JS_PUBLIC_API(double) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index ccaeda2a3..00a8abf84 100755 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -354,10 +354,22 @@ MakeDate(double day, double time) JS_PUBLIC_API(double) JS::MakeDate(double year, unsigned month, unsigned day) { + MOZ_ASSERT(month <= 11); + MOZ_ASSERT(day >= 1 && day <= 31); + return ::MakeDate(MakeDay(year, month, day), 0); } JS_PUBLIC_API(double) +JS::MakeDate(double year, unsigned month, unsigned day, double time) +{ + MOZ_ASSERT(month <= 11); + MOZ_ASSERT(day >= 1 && day <= 31); + + return ::MakeDate(MakeDay(year, month, day), time); +} + +JS_PUBLIC_API(double) JS::YearFromTime(double time) { return ::YearFromTime(time); |