From 3b8d24338c60cd9cfcda15ae599e9a809fedaa46 Mon Sep 17 00:00:00 2001 From: Raphael Catolino Date: Thu, 27 Dec 2012 18:55:31 +0000 Subject: [PATCH] Bug 769370 - Add valueAsDate attribute and implement valueAsNumber/valueAsDate for date. r=mounir --- .../html/content/src/nsHTMLInputElement.cpp | 182 ++++++++++++++-- content/html/content/src/nsHTMLInputElement.h | 22 ++ content/html/content/test/forms/Makefile.in | 1 + .../test_input_attributes_reflection.html | 4 +- .../forms/test_valueasdate_attribute.html | 206 ++++++++++++++++++ .../forms/test_valueasnumber_attribute.html | 108 ++++++++- .../html/nsIDOMHTMLInputElement.idl | 1 + 7 files changed, 503 insertions(+), 21 deletions(-) create mode 100644 content/html/content/test/forms/test_valueasdate_attribute.html diff --git a/content/html/content/src/nsHTMLInputElement.cpp b/content/html/content/src/nsHTMLInputElement.cpp index a653a53b41c..7106eee2386 100644 --- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -93,6 +93,9 @@ #include +// input type=date +#include "jsapi.h" + using namespace mozilla; using namespace mozilla::dom; @@ -1057,17 +1060,72 @@ nsHTMLInputElement::IsValueEmpty() const return value.IsEmpty(); } +bool +nsHTMLInputElement::ConvertStringToNumber(nsAString& aValue, + double& aResultValue) const +{ + switch (mType) { + case NS_FORM_INPUT_NUMBER: + { + nsresult ec; + aResultValue = PromiseFlatString(aValue).ToDouble(&ec); + if (NS_FAILED(ec)) { + return false; + } + + break; + } + case NS_FORM_INPUT_DATE: + { + JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc()); + if (!ctx) { + return false; + } + + uint32_t year, month, day; + if (!GetValueAsDate(aValue, year, month, day)) { + return false; + } + + JSObject* date = JS_NewDateObjectMsec(ctx, 0); + jsval rval; + jsval fullYear[3]; + fullYear[0].setInt32(year); + fullYear[1].setInt32(month-1); + fullYear[2].setInt32(day); + if (!JS::Call(ctx, date, "setUTCFullYear", 3, fullYear, &rval)) { + return false; + } + + jsval timestamp; + if (!JS::Call(ctx, date, "getTime", 0, nullptr, ×tamp)) { + return false; + } + + if (!timestamp.isNumber()) { + return false; + } + + aResultValue = timestamp.toNumber(); + } + break; + default: + return false; + } + + return true; +} + double nsHTMLInputElement::GetValueAsDouble() const { double doubleValue; nsAutoString stringValue; - nsresult ec; GetValueInternal(stringValue); - doubleValue = stringValue.ToDouble(&ec); - return NS_SUCCEEDED(ec) ? doubleValue : MOZ_DOUBLE_NaN(); + return !ConvertStringToNumber(stringValue, doubleValue) ? MOZ_DOUBLE_NaN() + : doubleValue; } NS_IMETHODIMP @@ -1143,10 +1201,100 @@ void nsHTMLInputElement::SetValue(double aValue) { nsAutoString value; - value.AppendFloat(aValue); + switch (mType) { + case NS_FORM_INPUT_NUMBER: + value.AppendFloat(aValue); + break; + case NS_FORM_INPUT_DATE: + { + value.Truncate(); + JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc()); + if (!ctx) { + break; + } + + JSObject* date = JS_NewDateObjectMsec(ctx, aValue); + if (!date) { + break; + } + + jsval year, month, day; + if(!JS::Call(ctx, date, "getUTCFullYear", 0, nullptr, &year)) { + break; + } + + if(!JS::Call(ctx, date, "getUTCMonth", 0, nullptr, &month)) { + break; + } + + if(!JS::Call(ctx, date, "getUTCDate", 0, nullptr, &day)) { + break; + } + + value.AppendPrintf("%04.0f-%02.0f-%02.0f", year.toNumber(), + month.toNumber() + 1, day.toNumber()); + } + break; + } + SetValue(value); } +NS_IMETHODIMP +nsHTMLInputElement::GetValueAsDate(JSContext* aCtx, jsval* aDate) +{ + if (mType != NS_FORM_INPUT_DATE) { + aDate->setNull(); + return NS_OK; + } + + uint32_t year, month, day; + nsAutoString value; + GetValueInternal(value); + if (!GetValueAsDate(value, year, month, day)) { + aDate->setNull(); + return NS_OK; + } + + JSObject* date = JS_NewDateObjectMsec(aCtx, 0); + jsval rval; + jsval fullYear[3]; + fullYear[0].setInt32(year); + fullYear[1].setInt32(month-1); + fullYear[2].setInt32(day); + if(!JS::Call(aCtx, date, "setUTCFullYear", 3, fullYear, &rval)) { + aDate->setNull(); + return NS_OK; + } + + aDate->setObjectOrNull(date); + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLInputElement::SetValueAsDate(JSContext* aCtx, const jsval& aDate) +{ + if (mType != NS_FORM_INPUT_DATE) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (!aDate.isObject() || !JS_ObjectIsDate(aCtx, &aDate.toObject())) { + SetValue(EmptyString()); + return NS_OK; + } + + JSObject& date = aDate.toObject(); + jsval timestamp; + bool ret = JS::Call(aCtx, &date, "getTime", 0, nullptr, ×tamp); + if (!ret || !timestamp.isNumber() || MOZ_DOUBLE_IS_NaN(timestamp.toNumber())) { + SetValue(EmptyString()); + return NS_OK; + } + + SetValue(timestamp.toNumber()); + return NS_OK; +} + NS_IMETHODIMP nsHTMLInputElement::GetValueAsNumber(double* aValueAsNumber) { @@ -2770,6 +2918,17 @@ nsHTMLInputElement::SanitizeValue(nsAString& aValue) bool nsHTMLInputElement::IsValidDate(nsAString& aValue) const { + uint32_t year, month, day; + return GetValueAsDate(aValue, year, month, day); +} + +bool +nsHTMLInputElement::GetValueAsDate(nsAString& aValue, + uint32_t& aYear, + uint32_t& aMonth, + uint32_t& aDay) const +{ + /* * Parse the year, month, day values out a date string formatted as 'yyy-mm-dd'. * -The year must be 4 or more digits long, and year > 0 @@ -2782,9 +2941,6 @@ nsHTMLInputElement::IsValidDate(nsAString& aValue) const return false; } - uint32_t year = 0; - uint32_t month = 0; - uint32_t day = 0; int32_t fieldMaxSize = 0; int32_t fieldMinSize = 4; enum { @@ -2818,10 +2974,10 @@ nsHTMLInputElement::IsValidDate(nsAString& aValue) const switch(field) { case YEAR: - year = PromiseFlatString(StringHead(aValue, offset)).ToInteger(&ec); + aYear = PromiseFlatString(StringHead(aValue, offset)).ToInteger(&ec); NS_ENSURE_SUCCESS(ec, false); - if (year <= 0) { + if (aYear <= 0) { return false; } @@ -2831,12 +2987,12 @@ nsHTMLInputElement::IsValidDate(nsAString& aValue) const fieldMinSize = 2; break; case MONTH: - month = PromiseFlatString(Substring(aValue, + aMonth = PromiseFlatString(Substring(aValue, offset-fieldSize, offset)).ToInteger(&ec); NS_ENSURE_SUCCESS(ec, false); - if (month < 1 || month > 12) { + if (aMonth < 1 || aMonth > 12) { return false; } @@ -2847,12 +3003,12 @@ nsHTMLInputElement::IsValidDate(nsAString& aValue) const fieldMaxSize = 1; break; case DAY: - day = PromiseFlatString(Substring(aValue, + aDay = PromiseFlatString(Substring(aValue, offset-fieldSize, offset + 1)).ToInteger(&ec); NS_ENSURE_SUCCESS(ec, false); - if (day < 1 || day > NumberOfDaysInMonth(month, year)) { + if (aDay < 1 || aDay > NumberOfDaysInMonth(aMonth, aYear)) { return false; } diff --git a/content/html/content/src/nsHTMLInputElement.h b/content/html/content/src/nsHTMLInputElement.h index b15afac7ea9..60db0fed763 100644 --- a/content/html/content/src/nsHTMLInputElement.h +++ b/content/html/content/src/nsHTMLInputElement.h @@ -560,6 +560,17 @@ protected: */ double GetValueAsDouble() const; + /** + * Convert a string to a number in a type specific way, + * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#concept-input-value-string-number + * ie parse a date string to a timestamp if type=date, + * or parse a number string to its value if type=number. + * @param aValue the string to be parsed. + * @param aResultValue the timestamp as a double. + * @result whether the parsing was successful. + */ + bool ConvertStringToNumber(nsAString& aValue, double& aResultValue) const; + /** * Parse a date string of the form yyyy-mm-dd * @param the string to be parsed. @@ -568,6 +579,17 @@ protected: */ bool IsValidDate(nsAString& aValue) const; + /** + * Parse a date string of the form yyyy-mm-dd + * @param the string to be parsed. + * @return the date in aYear, aMonth, aDay. + * @return whether the parsing was successful. + */ + bool GetValueAsDate(nsAString& aValue, + uint32_t& aYear, + uint32_t& aMonth, + uint32_t& aDay) const; + /** * This methods returns the number of days in a given month, for a given year. */ diff --git a/content/html/content/test/forms/Makefile.in b/content/html/content/test/forms/Makefile.in index 8c972e4e04c..448434a61cf 100644 --- a/content/html/content/test/forms/Makefile.in +++ b/content/html/content/test/forms/Makefile.in @@ -50,6 +50,7 @@ MOCHITEST_FILES = \ test_experimental_forms_pref.html \ test_input_number_value.html \ test_input_sanitization.html \ + test_valueasdate_attribute.html \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/content/html/content/test/forms/test_input_attributes_reflection.html b/content/html/content/test/forms/test_input_attributes_reflection.html index 5fa10c1e20f..286cc645db3 100644 --- a/content/html/content/test/forms/test_input_attributes_reflection.html +++ b/content/html/content/test/forms/test_input_attributes_reflection.html @@ -218,8 +218,8 @@ reflectString({ // .value doesn't reflect a content attribute. // .valueAsDate -todo("valueAsDate" in document.createElement("input"), - "valueAsDate isn't implemented yet"); +is("valueAsDate" in document.createElement("input"), true, + "valueAsDate should be available"); // Deeper check will be done with bug 763305. is('valueAsNumber' in document.createElement("input"), true, diff --git a/content/html/content/test/forms/test_valueasdate_attribute.html b/content/html/content/test/forms/test_valueasdate_attribute.html new file mode 100644 index 00000000000..6a8fb54c176 --- /dev/null +++ b/content/html/content/test/forms/test_valueasdate_attribute.html @@ -0,0 +1,206 @@ + + + + + Test for input.valueAsDate + + + + + +Mozilla Bug 769370 +

+
+
+
+ + diff --git a/content/html/content/test/forms/test_valueasnumber_attribute.html b/content/html/content/test/forms/test_valueasnumber_attribute.html index 76b4fb31978..253443a9131 100644 --- a/content/html/content/test/forms/test_valueasnumber_attribute.html +++ b/content/html/content/test/forms/test_valueasnumber_attribute.html @@ -4,7 +4,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=636737 --> - Test for Bug 636737 + Test for Bug input.valueAsNumber @@ -43,6 +43,7 @@ function checkAvailability() ["reset", false], ["button", false], ["number", true], + ["date", true], // The next types have not been implemented but will fallback to "text" // which has the same value. ["color", false], @@ -51,7 +52,6 @@ function checkAvailability() var todoList = [ ["datetime", true], - ["date", true], ["month", true], ["week", true], ["time", true], @@ -103,7 +103,7 @@ function checkAvailability() } } -function checkGet() +function checkNumberGet() { var testData = [ @@ -136,7 +136,7 @@ function checkGet() } } -function checkSet() +function checkNumberSet() { var testData = [ @@ -166,11 +166,107 @@ function checkSet() } } +function checkDateGet() +{ + var validData = + [ + [ "2012-07-12", 1342051200000 ], + [ "1970-01-01", 0 ], + // We are supposed to support at least until this date. + // (corresponding to the date object maximal value) + [ "275760-09-13", 8640000000000000 ], + // Minimum valid date (limited by the input element minimum valid value) + [ "0001-01-01", -62135596800000 ], + [ "2012-02-29", 1330473600000 ], + [ "2011-02-28", 1298851200000 ], + ]; + + var invalidData = + [ + "invaliddate", + "", + "275760-09-14", + "999-12-31", + "-001-12-31", + "0000-01-01", + "2011-02-29", + "1901-13-31", + "1901-12-32", + "1901-00-12", + "1901-01-00", + "1900-02-29", + ]; + + element.type = "date"; + for (data of validData) { + element.value = data[0]; + is(element.valueAsNumber, data[1], "valueAsNumber should return the " + + "timestamp representing this date"); + } + + for (data of invalidData) { + element.value = data; + ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " + + "when the element value is not a valid date"); + } +} + +function checkDateSet() +{ + var testData = + [ + [ 1342051200000, "2012-07-12" ], + [ 0, "1970-01-01" ], + // Maximum valid date (limited by the ecma date object range). + [ 8640000000000000, "275760-09-13" ], + // Minimum valid date (limited by the input element minimum valid value) + [ -62135596800000, "0001-01-01" ], + [ 1330473600000, "2012-02-29" ], + [ 1298851200000, "2011-02-28" ], + // "Values must be truncated to valid dates" + [ 42.1234, "1970-01-01" ], + [ 123.123456789123, "1970-01-01" ], + [ 1e2, "1970-01-01" ], + [ 1E9, "1970-01-12" ], + [ 1e-1, "1970-01-01" ], + [ 2e10, "1970-08-20" ], + [ 1298851200010, "2011-02-28" ], + [ -1, "1969-12-31" ], + [ -86400000, "1969-12-31" ], + [ 86400000, "1970-01-02" ], + // Invalid numbers. + // Those are implicitly converted to numbers + [ "", "1970-01-01" ], + [ true, "1970-01-01" ], + [ false, "1970-01-01" ], + [ null, "1970-01-01" ], + // Those are converted to NaN, the corresponding date string is the empty string + [ "invaliddatenumber", "" ], + [ NaN, "" ], + [ undefined, "" ], + // Out of range, the corresponding date string is the empty string + [ -62135596800001, "" ], + ]; + + element.type = "date"; + for (data of testData) { + element.valueAsNumber = data[0]; + is(element.value, data[1], "valueAsNumber should set the value to " + data[1]); + } + +} + SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() { checkAvailability(); -checkGet(); -checkSet(); + +// test +checkNumberGet(); +checkNumberSet(); + +// test +checkDateGet(); +checkDateSet(); SimpleTest.finish(); }); diff --git a/dom/interfaces/html/nsIDOMHTMLInputElement.idl b/dom/interfaces/html/nsIDOMHTMLInputElement.idl index 3cfae14a6a4..b5c1537ec4a 100644 --- a/dom/interfaces/html/nsIDOMHTMLInputElement.idl +++ b/dom/interfaces/html/nsIDOMHTMLInputElement.idl @@ -69,6 +69,7 @@ interface nsIDOMHTMLInputElement : nsIDOMHTMLElement attribute DOMString defaultValue; attribute DOMString value; attribute double valueAsNumber; + [implicit_jscontext] attribute jsval valueAsDate; [optional_argc] void stepDown([optional] in long n); [optional_argc] void stepUp([optional] in long n);