Bug 635553 (2/2) - <input type='number'> can suffer from a range underflow when @min is set. r=sicking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 22 Jun 2012 11:38:20 +0200
changeset 98377 3cc6b0905611
parent 98376 e5fd860299aa
child 98378 c49c1f667e7a
push id23053
push usermlamouri@mozilla.com
push dateThu, 05 Jul 2012 14:52:19 +0000
treeherdermozilla-central@ee2c5f2928b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs635553
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 635553 (2/2) - <input type='number'> can suffer from a range underflow when @min is set. r=sicking
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/src/nsIConstraintValidation.cpp
content/html/content/test/forms/Makefile.in
content/html/content/test/forms/test_min_attribute.html
dom/locales/en-US/chrome/dom/dom.properties
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -810,16 +810,18 @@ nsHTMLInputElement::AfterSetAttr(PRInt32
     } else if (MaxLengthApplies() && aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
     } else if (aName == nsGkAtoms::pattern) {
       UpdatePatternMismatchValidityState();
     } else if (aName == nsGkAtoms::multiple) {
       UpdateTypeMismatchValidityState();
     } else if (aName == nsGkAtoms::max) {
       UpdateRangeOverflowValidityState();
+    } else if (aName == nsGkAtoms::min) {
+      UpdateRangeUnderflowValidityState();
     }
 
     UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
                                                 aValue, aNotify);
 }
@@ -3795,16 +3797,40 @@ nsHTMLInputElement::IsRangeOverflow() co
   // value can be NaN when value="".
   if (value != value) {
     return false;
   }
 
   return value > max;
 }
 
+bool
+nsHTMLInputElement::IsRangeUnderflow() const
+{
+  nsAutoString minStr;
+  if (!DoesMinMaxApply() ||
+      !GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr)) {
+    return false;
+  }
+
+  PRInt32 ec;
+  double min = minStr.ToDouble(&ec);
+  if (NS_FAILED(ec)) {
+    return false;
+  }
+
+  double value = GetValueAsDouble();
+  // value can be NaN when value="".
+  if (value != value) {
+    return false;
+  }
+
+  return value < min;
+}
+
 void
 nsHTMLInputElement::UpdateTooLongValidityState()
 {
   // TODO: this code will be re-enabled with bug 613016 and bug 613019.
 #if 0
   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
 #endif
 }
@@ -3880,24 +3906,31 @@ nsHTMLInputElement::UpdatePatternMismatc
 
 void
 nsHTMLInputElement::UpdateRangeOverflowValidityState()
 {
   SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
 }
 
 void
+nsHTMLInputElement::UpdateRangeUnderflowValidityState()
+{
+  SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
+}
+
+void
 nsHTMLInputElement::UpdateAllValidityStates(bool aNotify)
 {
   bool validBefore = IsValid();
   UpdateTooLongValidityState();
   UpdateValueMissingValidityState();
   UpdateTypeMismatchValidityState();
   UpdatePatternMismatchValidityState();
   UpdateRangeOverflowValidityState();
+  UpdateRangeUnderflowValidityState();
 
   if (validBefore != IsValid()) {
     UpdateState(aNotify);
   }
 }
 
 void
 nsHTMLInputElement::UpdateBarredFromConstraintValidation()
@@ -4015,16 +4048,36 @@ nsHTMLInputElement::GetValidationMessage
 
       const PRUnichar* params[] = { maxStr.get() };
       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                  "FormValidationRangeOverflow",
                                                  params, message);
       aValidationMessage = message;
       break;
     }
+    case VALIDITY_STATE_RANGE_UNDERFLOW:
+    {
+      nsXPIDLString message;
+      nsAutoString minStr;
+      GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
+
+      // We want to show the double as parsed so we parse it and change minStr.
+      PRInt32 ec;
+      double min = minStr.ToDouble(&ec);
+      NS_ASSERTION(NS_SUCCEEDED(ec), "min must be a number at this point!");
+      minStr.Truncate();
+      minStr.AppendFloat(min);
+
+      const PRUnichar* params[] = { minStr.get() };
+      rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                                 "FormValidationRangeUnderflow",
+                                                 params, message);
+      aValidationMessage = message;
+      break;
+    }
     default:
       rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
   }
 
   return rv;
 }
 
 //static
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -212,21 +212,23 @@ public:
   }
 
   // nsIConstraintValidation
   bool     IsTooLong();
   bool     IsValueMissing() const;
   bool     HasTypeMismatch() const;
   bool     HasPatternMismatch() const;
   bool     IsRangeOverflow() const;
+  bool     IsRangeUnderflow() const;
   void     UpdateTooLongValidityState();
   void     UpdateValueMissingValidityState();
   void     UpdateTypeMismatchValidityState();
   void     UpdatePatternMismatchValidityState();
   void     UpdateRangeOverflowValidityState();
+  void     UpdateRangeUnderflowValidityState();
   void     UpdateAllValidityStates(bool aNotify);
   void     UpdateBarredFromConstraintValidation();
   nsresult GetValidationMessage(nsAString& aValidationMessage,
                                 ValidityStateType aType);
   /**
    * Update the value missing validity state for radio elements when they have
    * a group.
    *
--- a/content/html/content/src/nsIConstraintValidation.cpp
+++ b/content/html/content/src/nsIConstraintValidation.cpp
@@ -69,16 +69,18 @@ nsIConstraintValidation::GetValidationMe
     } else if (GetValidityState(VALIDITY_STATE_VALUE_MISSING)) {
       GetValidationMessage(aValidationMessage, VALIDITY_STATE_VALUE_MISSING);
     } else if (GetValidityState(VALIDITY_STATE_TYPE_MISMATCH)) {
       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 if (GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW)) {
+      GetValidationMessage(aValidationMessage, VALIDITY_STATE_RANGE_UNDERFLOW);
     } else {
       // TODO: The other messages have not been written
       // because related constraint validation are not implemented yet.
       // We should not be here.
       return NS_ERROR_UNEXPECTED;
     }
   } else {
     aValidationMessage.Truncate();
--- a/content/html/content/test/forms/Makefile.in
+++ b/content/html/content/test/forms/Makefile.in
@@ -38,13 +38,14 @@ include $(topsrcdir)/config/rules.mk
 		test_form_attribute-1.html \
 		test_form_attribute-2.html \
 		test_form_attribute-3.html \
 		test_form_attribute-4.html \
 		test_option_disabled.html \
 		test_meter_element.html \
 		test_meter_pseudo-classes.html \
 		test_max_attribute.html \
+		test_min_attribute.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/forms/test_min_attribute.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635553
+-->
+<head>
+  <title>Test for Bug 635553</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 635553 **/
+
+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.rangeUnderflow, !aValidity,
+     "element underflow status should be " + !aValidity);
+  is(aElement.validationMessage, aValidity
+       ? "" : "Please select a value that is higher than " + aElement.min + ".",
+     "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.min = '0';
+  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 = '0';
+    checkValidity(input, true);
+
+    input.value = 'foo';
+    checkValidity(input, true);
+
+    input.value = '-0.1';
+    checkValidity(input, !apply);
+
+    input.min = '-1';
+    checkValidity(input, true);
+
+    input.value = '-42';
+    checkValidity(input, !apply);
+  }
+
+  input.min = '';
+  checkValidity(input, true);
+
+  input.min = 'foo';
+  checkValidity(input, true);
+
+  // Check that we correctly convert input.min to a double in validationMessage.
+  if (input.type == 'number') {
+    input.min = "4.333333333333333333333333333333333331";
+    input.value = "2";
+    is(input.validationMessage,
+       "Please select a value that is higher than 4.33333333333333.",
+       "validation message");
+  }
+
+  // Cleaning up,
+  input.removeAttribute('min');
+  input.value = '';
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -37,16 +37,18 @@ FormValidationFileMissing=Please select 
 FormValidationSelectMissing=Please select an item in the list.
 FormValidationInvalidEmail=Please enter an email address.
 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.
+# LOCALIZATION NOTE (FormValidationRangeUnderflow): %S can be a number or a date.
+FormValidationRangeUnderflow=Please select a value that is higher 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.
 SetAttributeNodeNSWarning=Use of setAttributeNodeNS() is deprecated. Use setAttributeNS() instead.
 RemoveAttributeNodeWarning=Use of removeAttributeNode() is deprecated. Use removeAttribute() instead.
 CreateAttributeWarning=Use of document.createAttribute() is deprecated. Use element.setAttribute() instead.
 CreateAttributeNSWarning=Use of document.createAttributeNS() is deprecated. Use element.setAttributeNS() instead.
 SpecifiedWarning=Use of attributes' specified attribute is deprecated. It always returns true.