Bug 769370 - Add valueAsDate attribute and implement valueAsNumber/valueAsDate for date. r=mounir

This commit is contained in:
Raphael Catolino 2012-12-27 18:55:31 +00:00
parent 5ef44d47fd
commit 3b8d24338c
7 changed files with 503 additions and 21 deletions

View File

@ -93,6 +93,9 @@
#include <limits>
// 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, &timestamp)) {
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, &timestamp);
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;
}

View File

@ -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.
*/

View File

@ -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

View File

@ -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,

View File

@ -0,0 +1,206 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=769370
-->
<head>
<title>Test for input.valueAsDate</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=769370">Mozilla Bug 769370</a>
<p id="display"></p>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 769370**/
/**
* This test is checking .valueAsDate.
*/
var element = document.createElement("input");
function checkAvailability()
{
var testData =
[
["text", false],
["password", false],
["search", false],
["telephone", false],
["email", false],
["url", false],
["hidden", false],
["checkbox", false],
["radio", false],
["file", false],
["submit", false],
["image", false],
["reset", false],
["button", false],
["number", false],
["date", true],
// The next types have not been implemented but will fallback to "text"
// which has the same value.
["range", false],
["color", false],
];
var todoList =
[
["datetime", true],
["month", true],
["week", true],
["time", true],
["datetime-local", true],
];
for (data of testData) {
var exceptionCatched = false;
element.type = data[0];
try {
element.valueAsDate;
} catch (e) {
exceptionCatched = true;
}
is(exceptionCatched, false,
"valueAsDate shouldn't throw exception on getting");
exceptionCatched = false;
try {
element.valueAsDate = new Date();
} catch (e) {
exceptionCatched = true;
}
is(exceptionCatched, !data[1], "valueAsDate for " + data[0] +
" availability is not correct");
}
for (data of todoList) {
var exceptionCatched = false;
element.type = data[0];
try {
element.valueAsDate;
} catch (e) {
exceptionCatched = true;
}
is(exceptionCatched, false,
"valueAsDate shouldn't throw exception on getting");
exceptionCatched = false;
try {
element.valueAsDate= 42;
} catch (e) {
exceptionCatched = true;
}
todo_is(exceptionCatched, !data[1],
"valueAsDate for " + data[0] + " availability is not correct");
}
}
function checkGet()
{
var validData =
[
[ "2012-07-12", 1342051200000 ],
[ "1970-01-01", 0 ],
[ "1970-01-02", 86400000 ],
[ "1969-12-31", -86400000 ],
[ "0311-01-31", -52350451200000 ],
[ "275760-09-13", 8640000000000000 ],
[ "0001-01-01", -62135596800000 ],
[ "2012-02-29", 1330473600000 ],
[ "2011-02-28", 1298851200000 ],
];
var invalidData =
[
[ "invaliddate" ],
[ "-001-12-31" ],
[ "901-12-31" ],
[ "1901-13-31" ],
[ "1901-12-32" ],
[ "1901-00-12" ],
[ "1901-01-00" ],
[ "1900-02-29" ],
[ "0000-01-01" ],
[ "" ],
// This date 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-14", true ],
];
element.type = "date";
for (data of validData) {
element.value = data[0];
is(element.valueAsDate.valueOf(), data[1],
"valueAsDate should return the " +
"valid date object representing this date");
}
for (data of invalidData) {
element.value = data[0];
is(element.valueAsDate, data[1] ? "Invalid Date" : null,
"valueAsDate should return null " +
"when the element value is not a valid date");
}
}
function checkSet()
{
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" ],
[ 1e-1, "1970-01-01" ],
[ 1298851200010, "2011-02-28" ],
[ -1, "1969-12-31" ],
[ -86400000, "1969-12-31" ],
[ 86400000, "1970-01-02" ],
// Negative years, this is out of range for the input element,
// the corresponding date string is the empty string
[ -62135596800001, "" ],
// Invalid dates.
[ NaN, "" ],
];
element.type = "date";
for (data of testData) {
element.valueAsDate = new Date(data[0]);
is(element.value, data[1], "valueAsDate should set the value to "
+ data[1]);
}
element.valueAsDate = null;
is(element.value, "", "valueAsDate should set the value to the empty string");
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
checkAvailability();
checkGet();
checkSet();
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

View File

@ -4,7 +4,7 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=636737
-->
<head>
<title>Test for Bug 636737</title>
<title>Test for Bug input.valueAsNumber</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
@ -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();
// <input type='number'> test
checkNumberGet();
checkNumberSet();
// <input type='date'> test
checkDateGet();
checkDateSet();
SimpleTest.finish();
});

View File

@ -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);