зеркало из https://github.com/mozilla/gecko-dev.git
Bug 781570 - Part 1/2 - Implement .valueAsNumber for <input type='time'>. r=smaug
This commit is contained in:
Родитель
96ff7c3908
Коммит
b47bb67f41
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче