Bug 781570 - Part 1/2 - Implement .valueAsNumber for <input type='time'>. r=smaug

This commit is contained in:
Mounir Lamouri 2013-01-22 18:22:33 +00:00
Родитель 96ff7c3908
Коммит b47bb67f41
3 изменённых файлов: 196 добавлений и 8 удалений

Просмотреть файл

@ -1070,6 +1070,9 @@ bool
nsHTMLInputElement::ConvertStringToNumber(nsAString& aValue, nsHTMLInputElement::ConvertStringToNumber(nsAString& aValue,
double& aResultValue) const double& aResultValue) const
{ {
MOZ_ASSERT(DoesValueAsNumberApply(),
"ConvertStringToNumber only applies if .valueAsNumber applies");
switch (mType) { switch (mType) {
case NS_FORM_INPUT_NUMBER: case NS_FORM_INPUT_NUMBER:
{ {
@ -1120,6 +1123,14 @@ nsHTMLInputElement::ConvertStringToNumber(nsAString& aValue,
aResultValue = timestamp.toNumber(); aResultValue = timestamp.toNumber();
return true; return true;
} }
case NS_FORM_INPUT_TIME:
uint32_t milliseconds;
if (!ParseTime(aValue, &milliseconds)) {
return false;
}
aResultValue = static_cast<double>(milliseconds);
return true;
default: default:
return false; return false;
} }
@ -1228,8 +1239,8 @@ bool
nsHTMLInputElement::ConvertNumberToString(double aValue, nsHTMLInputElement::ConvertNumberToString(double aValue,
nsAString& aResultString) const nsAString& aResultString) const
{ {
MOZ_ASSERT(mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_NUMBER, MOZ_ASSERT(DoesValueAsNumberApply(),
"ConvertNumberToString is only implemented for type='{number,date}'"); "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(aValue) && !MOZ_DOUBLE_IS_INFINITE(aValue), MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(aValue) && !MOZ_DOUBLE_IS_INFINITE(aValue),
"aValue must be a valid non-Infinite number."); "aValue must be a valid non-Infinite number.");
@ -1273,6 +1284,36 @@ nsHTMLInputElement::ConvertNumberToString(double aValue,
return true; return true;
} }
case NS_FORM_INPUT_TIME:
{
// 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(floor(aValue), 86400000);
uint16_t milliseconds = value % 1000;
value /= 1000;
uint8_t seconds = value % 60;
value /= 60;
uint8_t minutes = value % 60;
value /= 60;
uint8_t hours = value;
if (milliseconds != 0) {
aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
hours, minutes, seconds, milliseconds);
} else if (seconds != 0) {
aResultString.AppendPrintf("%02d:%02d:%02d",
hours, minutes, seconds);
} else {
aResultString.AppendPrintf("%02d:%02d", hours, minutes);
}
return true;
}
default: default:
MOZ_NOT_REACHED(); MOZ_NOT_REACHED();
return false; return false;
@ -3106,6 +3147,12 @@ nsHTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
bool bool
nsHTMLInputElement::IsValidTime(const nsAString& aValue) const nsHTMLInputElement::IsValidTime(const nsAString& aValue) const
{
return ParseTime(aValue, nullptr);
}
/* static */ bool
nsHTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
{ {
/* The string must have the following parts: /* The string must have the following parts:
* - HOURS: two digits, value being in [0, 23]; * - HOURS: two digits, value being in [0, 23];
@ -3140,6 +3187,9 @@ nsHTMLInputElement::IsValidTime(const nsAString& aValue) const
} }
if (aValue.Length() == 5) { if (aValue.Length() == 5) {
if (aResult) {
*aResult = ((hours * 60) + minutes) * 60000;
}
return true; return true;
} }
@ -3154,6 +3204,9 @@ nsHTMLInputElement::IsValidTime(const nsAString& aValue) const
} }
if (aValue.Length() == 8) { if (aValue.Length() == 8) {
if (aResult) {
*aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
}
return true; return true;
} }
@ -3164,7 +3217,18 @@ nsHTMLInputElement::IsValidTime(const nsAString& aValue) const
} }
uint32_t fractionsSeconds; uint32_t fractionsSeconds;
return DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds); if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds)) {
return false;
}
if (aResult) {
*aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
// NOTE: there is 10.0 instead of 10 and static_cast<int> because
// some old [and stupid] compilers can't just do the right thing.
fractionsSeconds * pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
}
return true;
} }
bool bool

Просмотреть файл

@ -496,7 +496,7 @@ protected:
/** /**
* Returns if valueAsNumber attribute applies for the current type. * Returns if valueAsNumber attribute applies for the current type.
*/ */
bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); } bool DoesValueAsNumberApply() const { return DoesMinMaxApply() || mType == NS_FORM_INPUT_TIME; }
/** /**
* Returns if the maxlength attribute applies for the current type. * Returns if the maxlength attribute applies for the current type.
@ -635,6 +635,19 @@ protected:
*/ */
bool IsValidTime(const nsAString& aValue) const; bool IsValidTime(const nsAString& aValue) const;
/**
* Returns the time expressed in milliseconds of |aValue| being parsed as a
* time following the HTML specifications:
* http://www.whatwg.org/specs/web-apps/current-work/#parse-a-time-string
*
* Note: |aResult| can be null.
*
* @param aValue the string to be parsed.
* @param aResult the time expressed in milliseconds representing the time [out]
* @return Whether the parsing was successful.
*/
static bool ParseTime(const nsAString& aValue, uint32_t* aResult);
/** /**
* Sets the value of the element to the string representation of the double. * Sets the value of the element to the string representation of the double.
* *

Просмотреть файл

@ -21,9 +21,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=636737
* This test is checking .valueAsNumber. * This test is checking .valueAsNumber.
*/ */
// Global variable used by all functions.
var element = document.createElement("input");
function checkAvailability() function checkAvailability()
{ {
var testData = var testData =
@ -44,6 +41,7 @@ function checkAvailability()
["button", false], ["button", false],
["number", true], ["number", true],
["date", true], ["date", true],
["time", true],
// The next types have not been implemented but will fallback to "text" // The next types have not been implemented but will fallback to "text"
// which has the same value. // which has the same value.
["color", false], ["color", false],
@ -54,11 +52,11 @@ function checkAvailability()
["datetime", true], ["datetime", true],
["month", true], ["month", true],
["week", true], ["week", true],
["time", true],
["datetime-local", true], ["datetime-local", true],
["range", true], ["range", true],
]; ];
var element = document.createElement('input');
for (data of testData) { for (data of testData) {
var exceptionCatched = false; var exceptionCatched = false;
@ -123,6 +121,7 @@ function checkNumberGet()
["42,13", null], // comma can't be used as a decimal separator ["42,13", null], // comma can't be used as a decimal separator
]; ];
var element = document.createElement('input');
element.type = "number"; element.type = "number";
for (data of testData) { for (data of testData) {
element.value = data[0]; element.value = data[0];
@ -162,6 +161,7 @@ function checkNumberSet()
[NaN, ""], [NaN, ""],
]; ];
var element = document.createElement('input');
element.type = "number"; element.type = "number";
for (data of testData) { for (data of testData) {
var caught = false; var caught = false;
@ -213,6 +213,7 @@ function checkDateGet()
"1900-02-29", "1900-02-29",
]; ];
var element = document.createElement('input');
element.type = "date"; element.type = "date";
for (data of validData) { for (data of validData) {
element.value = data[0]; element.value = data[0];
@ -268,6 +269,7 @@ function checkDateSet()
[ -Infinity, "2011-02-28", true ], [ -Infinity, "2011-02-28", true ],
]; ];
var element = document.createElement('input');
element.type = "date"; element.type = "date";
for (data of testData) { for (data of testData) {
var caught = false; var caught = false;
@ -289,6 +291,111 @@ function checkDateSet()
} }
function checkTimeGet()
{
var tests = [
// Some invalid values to begin.
{ value: "", result: NaN },
{ value: "foobar", result: NaN },
{ value: "00:", result: NaN },
{ value: "24:00", result: NaN },
{ value: "00:99", result: NaN },
{ value: "00:00:", result: NaN },
{ value: "00:00:99", result: NaN },
{ value: "00:00:00:", result: NaN },
{ value: "00:00:00.", result: NaN },
{ value: "00:00:00.0000", result: NaN },
// Some simple valid values.
{ value: "00:00", result: 0 },
{ value: "00:01", result: 60000 },
{ value: "01:00", result: 3600000 },
{ value: "01:01", result: 3660000 },
{ value: "13:37", result: 49020000 },
// Valid values including seconds.
{ value: "00:00:01", result: 1000 },
{ value: "13:37:42", result: 49062000 },
// Valid values including seconds fractions.
{ value: "00:00:00.001", result: 1 },
{ value: "00:00:00.123", result: 123 },
{ value: "00:00:00.100", result: 100 },
{ value: "00:00:00.000", result: 0 },
{ value: "20:17:31.142", result: 73051142 },
// Highest possible value.
{ value: "23:59:59.999", result: 86399999 },
// Some values with one or two digits for the fraction of seconds.
{ value: "00:00:00.1", result: 100 },
{ value: "00:00:00.14", result: 140 },
{ value: "13:37:42.7", result: 49062700 },
{ value: "23:31:12.23", result: 84672230 },
];
var element = document.createElement('input');
element.type = 'time';
for (test of tests) {
element.value = test.value;
if (isNaN(test.result)) {
ok(isNaN(element.valueAsNumber),
"invalid value should have .valueAsNumber return NaN");
} else {
is(element.valueAsNumber, test.result,
".valueAsNumber should return " + test.result);
}
}
}
function checkTimeSet()
{
var tests = [
// Some NaN values (should set to empty string).
{ value: NaN, result: "" },
{ value: "foobar", result: "" },
{ value: function() {}, result: "" },
// Inifinity (should throw).
{ value: Infinity, throw: true },
{ value: -Infinity, throw: true },
// "" converts to 0... JS is fun :)
{ value: "", result: "00:00" },
// Simple tests.
{ value: 0, result: "00:00" },
{ value: 1, result: "00:00:00.001" },
{ value: 100, result: "00:00:00.100" },
{ value: 1000, result: "00:00:01" },
{ value: 60000, result: "00:01" },
{ value: 3600000, result: "01:00" },
{ value: 83622234, result: "23:13:42.234" },
// Some edge cases.
{ value: 86400000, result: "00:00" },
{ value: 86400001, result: "00:00:00.001" },
{ value: 170022234, result: "23:13:42.234" },
{ value: 432000000, result: "00:00" },
{ value: -1, result: "23:59:59.999" },
{ value: -86400000, result: "00:00" },
{ value: -86400001, result: "23:59:59.999" },
{ value: -56789, result: "23:59:03.211" },
{ value: 0.9, result: "00:00" },
];
var element = document.createElement('input');
element.type = 'time';
for (test of tests) {
try {
var caught = false;
element.valueAsNumber = test.value;
is(element.value, test.result, "value should return " + test.result);
} catch(e) {
caught = true;
}
if (!test.throw) {
test.throw = false;
}
is(caught, test.throw, "the test throwing status should be " + test.throw);
}
}
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() { SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
checkAvailability(); checkAvailability();
@ -301,6 +408,10 @@ checkNumberSet();
checkDateGet(); checkDateGet();
checkDateSet(); checkDateSet();
// <input type='time'> test
checkTimeGet();
checkTimeSet();
SimpleTest.finish(); SimpleTest.finish();
}); });