Bug 635499 (2/2) - <input type='number'> can suffer from a range overflow when @max is set. r=sicking

--HG--
extra : rebase_source : 8d5a589b3ab4018de422fb92ab436afc5a083fb7
This commit is contained in:
Mounir Lamouri 2012-06-22 11:36:24 +02:00
parent cfbdaec5a7
commit ccff7a0918
6 changed files with 277 additions and 0 deletions

View File

@ -90,6 +90,8 @@
#include "nsIIDNService.h"
#include <limits>
using namespace mozilla;
using namespace mozilla::dom;
@ -811,6 +813,8 @@ nsHTMLInputElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
UpdatePatternMismatchValidityState();
} else if (aName == nsGkAtoms::multiple) {
UpdateTypeMismatchValidityState();
} else if (aName == nsGkAtoms::max) {
UpdateRangeOverflowValidityState();
}
UpdateState(aNotify);
@ -975,6 +979,20 @@ nsHTMLInputElement::IsValueEmpty() const
return value.IsEmpty();
}
double
nsHTMLInputElement::GetValueAsDouble() const
{
double doubleValue;
nsAutoString stringValue;
PRInt32 ec;
GetValueInternal(stringValue);
doubleValue = stringValue.ToDouble(&ec);
return NS_FAILED(ec) ? std::numeric_limits<double>::quiet_NaN()
: doubleValue;
}
NS_IMETHODIMP
nsHTMLInputElement::SetValue(const nsAString& aValue)
{
@ -3593,6 +3611,42 @@ nsHTMLInputElement::DoesPatternApply() const
return IsSingleLineTextControl(false);
}
bool
nsHTMLInputElement::DoesMinMaxApply() const
{
switch (mType)
{
case NS_FORM_INPUT_NUMBER:
// TODO:
// case NS_FORM_INPUT_RANGE:
// All date/time types.
return true;
#ifdef DEBUG
case NS_FORM_INPUT_RESET:
case NS_FORM_INPUT_SUBMIT:
case NS_FORM_INPUT_IMAGE:
case NS_FORM_INPUT_BUTTON:
case NS_FORM_INPUT_HIDDEN:
case NS_FORM_INPUT_RADIO:
case NS_FORM_INPUT_CHECKBOX:
case NS_FORM_INPUT_FILE:
case NS_FORM_INPUT_TEXT:
case NS_FORM_INPUT_PASSWORD:
case NS_FORM_INPUT_SEARCH:
case NS_FORM_INPUT_TEL:
case NS_FORM_INPUT_EMAIL:
case NS_FORM_INPUT_URL:
return false;
default:
NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
return false;
#else // DEBUG
default:
return false;
#endif // DEBUG
}
}
// nsIConstraintValidation
NS_IMETHODIMP
@ -3721,6 +3775,30 @@ nsHTMLInputElement::HasPatternMismatch() const
return !nsContentUtils::IsPatternMatching(value, pattern, doc);
}
bool
nsHTMLInputElement::IsRangeOverflow() const
{
nsAutoString maxStr;
if (!DoesMinMaxApply() ||
!GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr)) {
return false;
}
PRInt32 ec;
double max = maxStr.ToDouble(&ec);
if (NS_FAILED(ec)) {
return false;
}
double value = GetValueAsDouble();
// value can be NaN when value="".
if (value != value) {
return false;
}
return value > max;
}
void
nsHTMLInputElement::UpdateTooLongValidityState()
{
@ -3799,6 +3877,12 @@ nsHTMLInputElement::UpdatePatternMismatchValidityState()
SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, HasPatternMismatch());
}
void
nsHTMLInputElement::UpdateRangeOverflowValidityState()
{
SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
}
void
nsHTMLInputElement::UpdateAllValidityStates(bool aNotify)
{
@ -3807,6 +3891,7 @@ nsHTMLInputElement::UpdateAllValidityStates(bool aNotify)
UpdateValueMissingValidityState();
UpdateTypeMismatchValidityState();
UpdatePatternMismatchValidityState();
UpdateRangeOverflowValidityState();
if (validBefore != IsValid()) {
UpdateState(aNotify);
@ -3914,6 +3999,26 @@ nsHTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
aValidationMessage = message;
break;
}
case VALIDITY_STATE_RANGE_OVERFLOW:
{
nsXPIDLString message;
nsAutoString maxStr;
GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
// We want to show the double as parsed so we parse it and change maxStr.
PRInt32 ec;
double max = maxStr.ToDouble(&ec);
NS_ASSERTION(NS_SUCCEEDED(ec), "max must be a number at this point!");
maxStr.Truncate();
maxStr.AppendFloat(max);
const PRUnichar* params[] = { maxStr.get() };
rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
"FormValidationRangeOverflow",
params, message);
aValidationMessage = message;
break;
}
default:
rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
}

View File

@ -216,10 +216,12 @@ public:
bool IsValueMissing() const;
bool HasTypeMismatch() const;
bool HasPatternMismatch() const;
bool IsRangeOverflow() const;
void UpdateTooLongValidityState();
void UpdateValueMissingValidityState();
void UpdateTypeMismatchValidityState();
void UpdatePatternMismatchValidityState();
void UpdateRangeOverflowValidityState();
void UpdateAllValidityStates(bool aNotify);
void UpdateBarredFromConstraintValidation();
nsresult GetValidationMessage(nsAString& aValidationMessage,
@ -463,6 +465,11 @@ protected:
*/
bool DoesPatternApply() const;
/**
* Returns if the min and max attributes apply for the current type.
*/
bool DoesMinMaxApply() const;
/**
* Returns if the maxlength attribute applies for the current type.
*/
@ -533,6 +540,14 @@ protected:
*/
nsIRadioGroupContainer* GetRadioGroupContainer() const;
/**
* Returns the input element's value as a double-precision float.
* Returns NaN if the current element's value is not a floating point number.
*
* @return the input element's value as a double-precision float.
*/
double GetValueAsDouble() const;
nsCOMPtr<nsIControllers> mControllers;
/*

View File

@ -72,6 +72,8 @@ nsIConstraintValidation::GetValidationMessage(nsAString& aValidationMessage)
GetValidationMessage(aValidationMessage, VALIDITY_STATE_TYPE_MISMATCH);
} else if (GetValidityState(VALIDITY_STATE_PATTERN_MISMATCH)) {
GetValidationMessage(aValidationMessage, VALIDITY_STATE_PATTERN_MISMATCH);
} else if (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW)) {
GetValidationMessage(aValidationMessage, VALIDITY_STATE_RANGE_OVERFLOW);
} else {
// TODO: The other messages have not been written
// because related constraint validation are not implemented yet.

View File

@ -42,6 +42,7 @@ _TEST_FILES = \
test_option_disabled.html \
test_meter_element.html \
test_meter_pseudo-classes.html \
test_max_attribute.html \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,152 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=635499
-->
<head>
<title>Test for Bug 635499</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=635499">Mozilla Bug 635499</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 635499 **/
var types = [
[ 'hidden', false ],
[ 'text', false ],
[ 'search', false ],
[ 'tel', false ],
[ 'url', false ],
[ 'email', false ],
[ 'password', false ],
[ 'datetime', true, true ],
[ 'date', true, true ],
[ 'month', true, true ],
[ 'week', true, true ],
[ 'time', true, true ],
[ 'datetime-local', true, true ],
[ 'number', true ],
[ 'range', true, true ],
[ 'color', false, true ],
[ 'checkbox', false ],
[ 'radio', false ],
[ 'file', false ],
[ 'submit', false ],
[ 'image', false ],
[ 'reset', false ],
[ 'button', false ],
];
var input = document.createElement("input");
document.getElementById('content').appendChild(input);
function checkValidity(aElement, aValidity)
{
is(aElement.validity.valid, aValidity,
"element validity should be " + aValidity);
is(aElement.validity.rangeOverflow, !aValidity,
"element overflow status should be " + !aValidity);
is(aElement.validationMessage, aValidity
? "" : "Please select a value that is lower than " + aElement.max + ".",
"validation message");
is(aElement.mozMatchesSelector(":valid"), aElement.willValidate && aValidity,
(aElement.willValidate && aValidity) ? ":valid should apply" : "valid shouldn't apply");
is(aElement.mozMatchesSelector(":invalid"), aElement.willValidate && !aValidity,
(aElement.wil && aValidity) ? ":invalid shouldn't apply" : "valid should apply");
// TODO: Add tests for out-of-range / in-range selectors, see bug 635554.
}
for each (var data in types) {
input.type = data[0];
var apply = data[1];
if (data[2]) {
todo_is(input.type, data[0], data[0] + " isn't implemented yet");
continue;
}
checkValidity(input, true);
input.max = '2';
checkValidity(input, true);
if (input.type == 'url') {
input.value = 'http://mozilla.org';
checkValidity(input, true);
} else if (input.type == 'email') {
input.value = 'foo@bar.com';
checkValidity(input, true);
} else if (input.type == 'file') {
// Need privileges to set a filename with .value.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var file = dirSvc.get("ProfD", Components.interfaces.nsIFile);
file.append('635499_file');
var outStream = Components.
classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
outStream.init(file, 0x02 | 0x08 | 0x20, // write, create, truncate
0666, 0);
outStream.write("foo", 3);
outStream.close();
input.value = file.path;
checkValidity(input, true);
file.remove(false);
} else {
input.value = '1';
checkValidity(input, true);
input.value = '2';
checkValidity(input, true);
input.value = 'foo';
checkValidity(input, true);
input.value = '2.1';
checkValidity(input, !apply);
input.max = '5';
checkValidity(input, true);
input.value = '42';
checkValidity(input, !apply);
}
input.max = '';
checkValidity(input, true);
input.max = 'foo';
checkValidity(input, true);
// Check that we correctly convert input.max to a double in validationMessage.
if (input.type == 'number') {
input.max = "4.333333333333333333333333333333333331";
input.value = "5";
is(input.validationMessage,
"Please select a value that is lower than 4.33333333333333.",
"validation message");
}
// Cleaning up,
input.removeAttribute('max');
input.value = '';
}
</script>
</pre>
</body>
</html>

View File

@ -40,6 +40,8 @@ FormValidationInvalidURL=Please enter a URL.
FormValidationPatternMismatch=Please match the requested format.
# LOCALIZATION NOTE (FormValidationPatternMismatchWithTitle): %S is the (possibly truncated) title attribute value.
FormValidationPatternMismatchWithTitle=Please match the requested format: %S.
# LOCALIZATION NOTE (FormValidationRangeOverflow): %S can be a number or a date.
FormValidationRangeOverflow=Please select a value that is lower than %S.
GetAttributeNodeWarning=Use of getAttributeNode() is deprecated. Use getAttribute() instead.
SetAttributeNodeWarning=Use of setAttributeNode() is deprecated. Use setAttribute() instead.
GetAttributeNodeNSWarning=Use of getAttributeNodeNS() is deprecated. Use getAttributeNS() instead.