Bug 976739 - Allow user initiated stepping of <input type=number> when its value is the empty string (even if it is invalid due to the 'required' attribute being present) and step correctly if the value 0 (the default for the empty string) is not on a step. r=smaug

This commit is contained in:
Jonathan Watt 2014-02-26 23:04:31 +00:00
Родитель 807ab2beae
Коммит 64f3cbe833
3 изменённых файлов: 62 добавлений и 14 удалений

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

@ -2140,7 +2140,15 @@ HTMLInputElement::GetValueIfStepped(int32_t aStep,
return NS_OK;
}
if (GetValidityState(VALIDITY_STATE_STEP_MISMATCH) &&
// If the current value isn't aligned on a step, then shift the value to the
// nearest step that will cause the addition of aStep steps (further below)
// to |value| to hit the required value.
// (Instead of using GetValidityState(VALIDITY_STATE_STEP_MISMATCH) we have
// to check HasStepMismatch and pass true as its aUseZeroIfValueNaN argument
// since we need to treat the value "" as zero for stepping purposes even
// though we don't suffer from a step mismatch when our value is the empty
// string.)
if (HasStepMismatch(true) &&
value != minimum && value != maximum) {
if (aStep > 0) {
value -= NS_floorModulo(value - GetStepBase(), step);
@ -3641,13 +3649,21 @@ HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
// want to wipe out what they typed if they try to increment/decrement the
// value. Better is to highlight the value as being invalid so that they
// can correct what they typed.
// We pass 'true' for UpdateValidityUIBits' aIsFocused argument regardless
// because we need the UI to update _now_ or the user will wonder why the
// step behavior isn't functioning.
// We only do this if there actually is a value typed in by/displayed to
// the user. (IsValid() can return false if the 'required' attribute is
// set and the value is the empty string.)
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame &&
!numberControlFrame->AnonTextControlIsEmpty()) {
// We pass 'true' for UpdateValidityUIBits' aIsFocused argument
// regardless because we need the UI to update _now_ or the user will
// wonder why the step behavior isn't functioning.
UpdateValidityUIBits(true);
UpdateState(true);
return;
}
}
Decimal newValue = Decimal::nan(); // unchanged if value will not change
@ -6433,7 +6449,7 @@ HTMLInputElement::IsRangeUnderflow() const
}
bool
HTMLInputElement::HasStepMismatch() const
HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
{
if (!DoesStepApply()) {
return false;
@ -6441,9 +6457,13 @@ HTMLInputElement::HasStepMismatch() const
Decimal value = GetValueAsDecimal();
if (value.isNaN()) {
if (aUseZeroIfValueNaN) {
value = 0;
} else {
// The element can't suffer from step mismatch if it's value isn't a number.
return false;
}
}
Decimal step = GetStep();
if (step == kStepAny) {

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

@ -256,7 +256,7 @@ public:
bool HasPatternMismatch() const;
bool IsRangeOverflow() const;
bool IsRangeUnderflow() const;
bool HasStepMismatch() const;
bool HasStepMismatch(bool aUseZeroIfValueNaN = false) const;
bool HasBadInput() const;
void UpdateTooLongValidityState();
void UpdateValueMissingValidityState();

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

@ -71,6 +71,18 @@ function getStepBase(element) {
Number(element.getAttribute("value") || "NaN") || 0;
}
function hasStepMismatch(element) {
var value = element.value;
if (value == "") {
value = 0;
}
var step = getStep(element);
if (step == "any") {
return false;
}
return ((value - getStepBase(element)) % step) != 0;
}
function floorModulo(x, y) {
return (x - y * Math.floor(x / y));
}
@ -101,7 +113,7 @@ function expectedValueAfterStepUpOrDown(stepFactor, element) {
return value;
}
if (element.validity.stepMismatch &&
if (hasStepMismatch(element) &&
value != minimum && value != maximum) {
if (stepFactor > 0) {
value -= floorModulo(value - getStepBase(element), step);
@ -138,8 +150,8 @@ function test() {
var elem = document.getElementById("input");
elem.focus();
elem.min = -3;
elem.max = 3;
elem.min = -5;
elem.max = 5;
elem.step = 2;
var defaultValue = 0;
var oldVal, expectedVal;
@ -203,6 +215,22 @@ function test() {
sendString("abc");
synthesizeKey(key, {});
is(elem.value, "", "Test " + key + " does nothing when the input is invalid");
// Test that no value does not block UI initiated stepping:
oldVal = elem.value = "";
elem.setAttribute("required", "required");
elem.select();
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set");
// Same again:
expectedVal = expectedValAfterKeyEvent(key, elem);
synthesizeKey(key, {});
is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
// Reset 'required' attribute:
elem.removeAttribute("required");
}
}