Bug 836323 - Mochitest additions and changes for <input type=range>. r=mounir.
authorJonathan Watt <jwatt@jwatt.org>
Sat, 16 Feb 2013 12:36:02 +0000
changeset 122130 fb7e4cc681084ad81d3396a2a588c19f63a4c635
parent 122129 125640d9063135da06af7039d235edae1580c0e9
child 122131 2eba5f66565a6d476f7448257007de04a9bd8047
push id24319
push userryanvm@gmail.com
push dateSat, 16 Feb 2013 23:49:46 +0000
treeherdermozilla-central@c4de6de47382 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir
bugs836323
milestone21.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 836323 - Mochitest additions and changes for <input type=range>. r=mounir.
build/automation.py.in
content/html/content/test/forms/test_input_attributes_reflection.html
content/html/content/test/forms/test_input_sanitization.html
content/html/content/test/forms/test_max_attribute.html
content/html/content/test/forms/test_min_attribute.html
content/html/content/test/forms/test_mozistextfield.html
content/html/content/test/forms/test_pattern_attribute.html
content/html/content/test/forms/test_required_attribute.html
content/html/content/test/forms/test_step_attribute.html
content/html/content/test/forms/test_stepup_stepdown.html
content/html/content/test/forms/test_valueasdate_attribute.html
content/html/content/test/forms/test_valueasnumber_attribute.html
content/html/content/test/test_bug590363.html
content/html/content/test/test_bug598643.html
toolkit/components/passwordmgr/test/test_basic_form_html5.html
toolkit/components/satchel/test/test_form_autocomplete.html
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -438,16 +438,17 @@ user_pref("browser.console.showInPanel",
 user_pref("browser.dom.window.dump.enabled", true);
 user_pref("browser.firstrun.show.localepicker", false);
 user_pref("browser.firstrun.show.uidiscovery", false);
 user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.homepage
 user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
 user_pref("dom.allow_scripts_to_close_windows", true);
 user_pref("dom.disable_open_during_load", false);
 user_pref("dom.experimental_forms", true); // on for testing
+user_pref("dom.experimental_forms_range", true); // on for testing
 user_pref("dom.max_script_run_time", 0); // no slow script dialogs
 user_pref("hangmonitor.timeout", 0); // no hang monitor
 user_pref("dom.max_chrome_script_run_time", 0);
 user_pref("dom.popup_maximum", -1);
 user_pref("dom.send_after_paint_to_content", true);
 user_pref("dom.successive_dialog_time_limit", 0);
 user_pref("signed.applets.codebase_principal_support", true);
 user_pref("browser.shell.checkDefaultBrowser", false);
--- a/content/html/content/test/forms/test_input_attributes_reflection.html
+++ b/content/html/content/test/forms/test_input_attributes_reflection.html
@@ -199,21 +199,21 @@ reflectString({
 });
 
 // .type
 reflectLimitedEnumerated({
   element: document.createElement("input"),
   attribute: "type",
   validValues: [ "hidden", "text", "search", "tel", "url", "email", "password",
                  "checkbox", "radio", "file", "submit", "image", "reset",
-                 "button", "date", "time", "number" ],
+                 "button", "date", "time", "number", "range" ],
   invalidValues: [ "this-is-probably-a-wrong-type", "", "tulip" ],
   defaultValue: "text",
   unsupportedValues: [ "datetime", "month", "week", "datetime-local",
-                       "range", "color" ]
+                       "color" ]
 });
 
 // .defaultValue
 reflectString({
   element: document.createElement("input"),
   attribute: { idl: "defaultValue", content: "value" },
   otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
 });
--- a/content/html/content/test/forms/test_input_sanitization.html
+++ b/content/html/content/test/forms/test_input_sanitization.html
@@ -13,30 +13,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <pre id="test">
 <div id='content'>
   <form>
   </form>
 </div>
 <script type="application/javascript">
 
-/** Test for Bug 549475 **/
+/**
+ * This files tests the 'value sanitization algorithm' for the various input
+ * types. Note that an input's value is affected by more than just its type's
+ * value sanitization algorithm; e.g. some type=range has actions that the user
+ * agent must perform to change the element's value to avoid underflow/overflow
+ * and step mismatch (when possible). We specifically avoid triggering these
+ * other actions here so that this test only tests the value sanitization
+ * algorithm for the various input types.
+ *
+ * XXXjwatt splitting out testing of the value sanitization algorithm and 
+ * "other things" that affect .value makes it harder to know what we're testing
+ * and what we've missed, because what's included in the value sanitization
+ * algorithm and what's not is different from input type to input type. It
+ * seems to me it would be better to have a test (maybe one per type) focused
+ * on testing .value for permutations of all other inputs that can affect it.
+ * The value sanitization algorithm is just an internal spec concept after all.
+ */
 
 // We are excluding "file" because it's too different from the other types.
 // And it has no sanitizing algorithm.
 var inputTypes =
 [
   "text", "password", "search", "tel", "hidden", "checkbox", "radio",
   "submit", "image", "reset", "button", "email", "url", "number", "date",
-  "time",
+  "time", "range"
 ];
 
 var todoTypes =
 [
-  "range", "color",
+  "color",
   "month", "week", "datetime", "datetime-local",
 ];
 
 var valueModeValue =
 [
   "text", "search", "url", "tel", "email", "password", "date", "datetime",
   "month", "week", "time", "datetime-local", "number", "range", "color",
 ];
@@ -50,16 +66,30 @@ function sanitizeValue(aType, aValue)
     case "search":
     case "tel":
       return aValue.replace(/[\n\r]/g, "");
     case "url":
     case "email":
       return aValue.replace(/[\n\r]/g, "").replace(/^[\u0020\u0009\t\u000a\u000c\u000d]+|[\u0020\u0009\t\u000a\u000c\u000d]+$/g, "");
     case "number":
       return isNaN(Number(aValue)) ? "" : aValue;
+    case "range":
+      var defaultMinimum = 0;
+      var defaultMaximum = 100;
+      var value = Number(aValue);
+      if (isNaN(value)) {
+        return ((defaultMaximum - defaultMinimum)/2).toString(); // "50"
+      }
+      if (value < defaultMinimum) {
+        return defaultMinimum.toString();
+      }
+      if (value > defaultMaximum) {
+        return defaultMaximum.toString();
+      }
+      return aValue;
     case "date":
       // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
       function getNumbersOfDaysInMonth(aMonth, aYear) {
         if (aMonth === 2) {
           return (aYear % 400 === 0 || (aYear % 100 != 0 && aYear % 4 === 0)) ? 29 : 28;
         }
         return (aMonth === 1 || aMonth === 3 || aMonth === 5 || aMonth === 7 ||
                 aMonth === 8 || aMonth === 10 || aMonth === 12) ? 31 : 30;
@@ -116,20 +146,16 @@ function sanitizeValue(aType, aValue)
       return aValue;
     case "month":
     case "week":
     case "datetime":
     case "datetime-local":
       // TODO: write the sanitize algorithm.
       ok(false);
       return "";
-    case "range":
-      ok(false);
-      // TODO: write the sanitize algorithm.
-      return "";
     case "color":
       ok(false);
       // TODO: write the sanitize algorithm.
       return "";
     default:
       return aValue;
   }
 }
@@ -146,17 +172,17 @@ function checkSanitizing(element)
     // For url:
     "\r\n foobar    \n\r",
     "\u000B foo \u000B",
     "\u000A foo \u000A",
     "\u000C foo \u000C",
     "\u000d foo \u000d",
     "\u0020 foo \u0020",
     " \u0009 foo \u0009 ",
-    // For number:
+    // For number and range:
     "42",
     "13.37",
     "1.234567898765432",
     "12foo",
     "1e2",
     "3E42",
     // For date:
     "1970-01-01",
--- a/content/html/content/test/forms/test_max_attribute.html
+++ b/content/html/content/test/forms/test_max_attribute.html
@@ -29,30 +29,43 @@ var data = [
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
   { type: 'month', apply: true, todo: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
-  { type: 'range', apply: true, todo: true },
+  { type: 'range', apply: true },
   { type: 'color', apply: false, todo: true },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
   { type: 'file', apply: false },
   { type: 'submit', apply: false },
   { type: 'image', apply: false },
   { type: 'reset', apply: false },
   { type: 'button', apply: false },
 ];
 
 var input = document.createElement("input");
 document.getElementById('content').appendChild(input);
 
+/**
+ * @aValidity - boolean indicating whether the element is expected to be valid
+ *   (aElement.validity.valid is true) or not. The value passed is ignored and
+ *   overridden with true if aApply is false.
+ * @aApply - boolean indicating whether the min/max attributes apply to this
+ *   element type.
+ * @aRangeApply - A boolean that's set to true if the current input type is a
+ *   "[candidate] for constraint validation" and it "[has] range limitations"
+ *   per http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#selector-in-range
+ *   (in other words, one of the pseudo classes :in-range and :out-of-range
+ *   should apply (which, depends on aValidity)).
+ *   Else (neither :in-range or :out-of-range should match) set to false.
+ */
 function checkValidity(aElement, aValidity, aApply, aRangeApply)
 {
   aValidity = aApply ? aValidity : true;
 
   is(aElement.validity.valid, aValidity,
      "element validity should be " + aValidity);
   is(aElement.validity.rangeOverflow, !aValidity,
      "element overflow status should be " + !aValidity);
@@ -81,17 +94,25 @@ for (var test of data) {
   input.type = test.type;
   var apply = test.apply;
 
   if (test.todo) {
     todo_is(input.type, test.type, test.type + " isn't implemented yet");
     continue;
   }
 
-  checkValidity(input, true, apply, false);
+  // The element should be valid. Range should not apply when @min and @max are
+  // undefined, except if the input type is 'range' (since that type has a
+  // default minimum and maximum).
+  if (input.type == 'range') {
+    checkValidity(input, true, apply, true);
+  } else {
+    checkValidity(input, true, apply, false);
+  }
+  checkValidity(input, true, apply, test.type == 'range');
 
   switch (input.type) {
     case 'hidden':
     case 'text':
     case 'search':
     case 'password':
     case 'url':
     case 'tel':
@@ -107,16 +128,24 @@ for (var test of data) {
       input.max = '-1';
       break;
     case 'date':
       input.max = '2012-06-27';
       break;
     case 'time':
       input.max = '02:20';
       break;
+    case 'range':
+      // range is special, since setting max to -1 will make it invalid since
+      // it's default would then be 0, meaning it suffers from overflow.
+      input.max = '-1';
+      checkValidity(input, false, apply, apply);
+      // Now make it something that won't cause an error below:
+      input.max = '10';
+      break;
     default:
       ok(false, 'please, add a case for this new type (' + input.type + ')');
   }
 
   checkValidity(input, true, apply, apply);
 
   switch (input.type) {
     case 'text':
@@ -231,16 +260,56 @@ for (var test of data) {
         input.max = "4.333333333333333333333333333333333331";
         input.value = "5";
         is(input.validationMessage,
            "Please select a value that is lower than 4.33333333333333.",
            "validation message");
       }
 
       break;
+    case 'range':
+      input.max = '2';
+      input.value = '1';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '2';
+      checkValidity(input, true, apply, apply);
+
+      input.value = 'foo';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '3';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.max, "the value should have been set to max");
+
+      input.max = '5';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '42';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.max, "the value should have been set to max");
+
+      input.max = '';
+      checkValidity(input, true, apply, apply);
+
+      input.max = 'foo';
+      checkValidity(input, true, apply, apply);
+
+      // Check that we correctly convert input.max to a double in validationMessage.
+      input.step = 'any';
+      input.min = 5;
+      input.max = 0.66666666666666666666666666666666666
+      input.value = 1;
+      is(input.validationMessage,
+         "Please select a value that is lower than 0.666666666666667.",
+         "validation message")
+
+      break;
     case 'time':
       // Don't worry about that.
       input.step = 'any';
 
       input.max = '10:10';
       input.value = '10:09';
       checkValidity(input, true, apply, apply);
 
--- a/content/html/content/test/forms/test_min_attribute.html
+++ b/content/html/content/test/forms/test_min_attribute.html
@@ -29,30 +29,43 @@ var data = [
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
   { type: 'month', apply: true, todo: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
-  { type: 'range', apply: true, todo: true },
+  { type: 'range', apply: true },
   { type: 'color', apply: false, todo: true },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
   { type: 'file', apply: false },
   { type: 'submit', apply: false },
   { type: 'image', apply: false },
   { type: 'reset', apply: false },
   { type: 'button', apply: false },
 ];
 
 var input = document.createElement("input");
 document.getElementById('content').appendChild(input);
 
+/**
+ * @aValidity - boolean indicating whether the element is expected to be valid
+ *   (aElement.validity.valid is true) or not. The value passed is ignored and
+ *   overridden with true if aApply is false.
+ * @aApply - boolean indicating whether the min/max attributes apply to this
+ *   element type.
+ * @aRangeApply - A boolean that's set to true if the current input type is a
+ *   "[candidate] for constraint validation" and it "[has] range limitations"
+ *   per http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#selector-in-range
+ *   (in other words, one of the pseudo classes :in-range and :out-of-range
+ *   should apply (which, depends on aValidity)).
+ *   Else (neither :in-range or :out-of-range should match) set to false.
+ */
 function checkValidity(aElement, aValidity, aApply, aRangeApply)
 {
   aValidity = aApply ? aValidity : true;
 
   is(aElement.validity.valid, aValidity,
      "element validity should be " + aValidity);
   is(aElement.validity.rangeUnderflow, !aValidity,
      "element underflow status should be " + !aValidity);
@@ -81,18 +94,24 @@ for (var test of data) {
   input.type = test.type;
   var apply = test.apply;
 
   if (test.todo) {
     todo_is(input.type, test.type, test.type + " isn't implemented yet");
     continue;
   }
 
-  // The element should be valid. Range should not apply.
-  checkValidity(input, true, apply, false);
+  // The element should be valid. Range should not apply when @min and @max are
+  // undefined, except if the input type is 'range' (since that type has a
+  // default minimum and maximum).
+  if (input.type == 'range') {
+    checkValidity(input, true, apply, true);
+  } else {
+    checkValidity(input, true, apply, false);
+  }
 
   switch (input.type) {
     case 'hidden':
     case 'text':
     case 'search':
     case 'password':
     case 'url':
     case 'tel':
@@ -108,16 +127,21 @@ for (var test of data) {
       input.min = '999';
       break;
     case 'date':
       input.min = '2012-06-27';
       break;
     case 'time':
       input.min = '20:20';
       break;
+    case 'range':
+      // range is special, since setting min to 999 will make it invalid since
+      // it's default maximum is 100, its value would be 999, and it would
+      // suffer from overflow.
+      break;
     default:
       ok(false, 'please, add a case for this new type (' + input.type + ')');
   }
 
   // The element should still be valid and range should apply if it can.
   checkValidity(input, true, apply, apply);
 
   switch (input.type) {
@@ -229,16 +253,52 @@ for (var test of data) {
       // Check that we correctly convert input.min to a double in
       // validationMessage.
       input.min = "4.333333333333333333333333333333333331";
       input.value = "2";
       is(input.validationMessage,
          "Please select a value that is higher than 4.33333333333333.",
          "validation message");
       break;
+    case 'range':
+      input.min = '0';
+      input.value = '1';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '0';
+      checkValidity(input, true, apply, apply);
+
+      input.value = 'foo';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '-1';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.min, "the value should have been set to min");
+
+      input.min = '-1';
+      checkValidity(input, true, apply, apply);
+
+      input.value = '-42';
+      checkValidity(input, true, apply, apply);
+
+      is(input.value, input.min, "the value should have been set to min");
+
+      input.min = '';
+      checkValidity(input, true, apply, true);
+
+      input.min = 'foo';
+      checkValidity(input, true, apply, true);
+
+      // We don't check the conversion of input.min to a double in
+      // validationMessage for 'range' since range will always clamp the value
+      // up to at least the minimum (so we will never see the min in a
+      // validationMessage).
+
+      break;
     case 'time':
       // Don't worry about that.
       input.step = 'any';
 
       input.min = '20:20';
       input.value = '20:20:01';
       checkValidity(input, true, apply, apply);
 
--- a/content/html/content/test/forms/test_mozistextfield.html
+++ b/content/html/content/test/forms/test_mozistextfield.html
@@ -43,27 +43,27 @@ var gInputTestData = [
   ['reset',    false],
   ['image',    false],
   ['radio',    false],
   ['submit',   false],
   ['search',   true],
   ['email',    true],
   ['url',      true],
   ['number',   false],
+  ['range',    false],
   ['date',     false],
   ['time',     false],
 ];
 
 /**
  * TODO: the next types are not yet in the tree.
  * The value is only a suggestion.
  */
 var gInputTodoData = [
 /* type        expected result */
-  ['range',    false],
   ['color',    false],
   ['datetime', false],
   ['month',    false],
   ['week',     false],
   ['datetime-local', false],
 ];
 
 function checkMozIsTextFieldDefined(aElement, aResult)
--- a/content/html/content/test/forms/test_pattern_attribute.html
+++ b/content/html/content/test/forms/test_pattern_attribute.html
@@ -256,18 +256,18 @@ function checkPatternValidity(element)
 }
 
 var input = document.getElementById('i');
 
 // |validTypes| are the types which accept @pattern
 // and |invalidTypes| are the ones which do not accept it.
 var validTypes = Array('text', 'password', 'search', 'tel', 'email', 'url');
 var barredTypes = Array('hidden', 'reset', 'button', 'submit', 'image');
-var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'date', 'time');
-// TODO: 'datetime', 'month', 'week', 'datetime-local', 'range' and 'color'
+var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'range', 'date', 'time');
+// TODO: 'datetime', 'month', 'week', 'datetime-local' and 'color'
 //       do not accept the @pattern too but are not implemented yet.
 
 for (type of validTypes) {
   input.type = type;
   completeValidityCheck(input, false);
   checkPatternValidity(input);
 }
 
--- a/content/html/content/test/forms/test_required_attribute.html
+++ b/content/html/content/test/forms/test_required_attribute.html
@@ -362,18 +362,18 @@ checkTextareaRequiredValidity();
 // First of all, checks for types that make the element barred from
 // constraint validation.
 var typeBarredFromConstraintValidation = ["hidden", "button", "reset", "submit", "image"];
 for (type of typeBarredFromConstraintValidation) {
   checkInputRequiredNotApply(type, true);
 }
 
 // Then, checks for the types which do not use the required attribute.
-// TODO: check 'color' and 'range' when they will be implemented.
-var typeRequireNotApply = [];
+// TODO: check 'color' when they will be implemented.
+var typeRequireNotApply = ['range'];
 for (type of typeRequireNotApply) {
   checkInputRequiredNotApply(type, false);
 }
 
 // Now, checking for all types which accept the required attribute.
 // TODO: check 'datetime', 'month', 'week' and 'datetime-local'
 //       when they will be implemented.
 var typeRequireApply = ["text", "password", "search", "tel", "email", "url",
--- a/content/html/content/test/forms/test_step_attribute.html
+++ b/content/html/content/test/forms/test_step_attribute.html
@@ -29,17 +29,17 @@ var data = [
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
   { type: 'month', apply: true, todo: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
-  { type: 'range', apply: true, todo: true },
+  { type: 'range', apply: true },
   { type: 'color', apply: false, todo: true },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
   { type: 'file', apply: false },
   { type: 'submit', apply: false },
   { type: 'image', apply: false },
   { type: 'reset', apply: false },
   { type: 'button', apply: false },
@@ -412,16 +412,223 @@ for (var test of data) {
       input.min = '1';
       input.max = '10.9';
       input.value = '10';
 
       is(input.validationMessage, "Please select a valid value. " +
                                   "The nearest valid value is 9.",
          "The validation message should not include the higher value.");
       break;
+    case 'range':
+      // Range is special in that it clamps to valid values, so it is much
+      // rarer for it to be invalid.
+
+      // When step=0, the allowed value step is 1.
+      input.step = '0';
+      input.value = '1.2';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '0';
+      is(input.value, 0, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      // When step is NaN, the allowed step value is 1.
+      input.step = 'foo';
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '1.5';
+      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // When step is negative, the allowed step value is 1.
+      input.step = '-0.1';
+      is(input.value, 2, "check that the value still coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      // When step is missing, the allowed step value is 1.
+      input.removeAttribute('step');
+      input.value = '1.5';
+      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      // When step is 'any', all values are fine wrt to step.
+      input.step = 'any';
+      checkValidity(input, true, apply);
+
+      input.step = 'aNy';
+      input.value = '97';
+      is(input.value, 97, "check that the value for step=aNy is unchanged");
+      checkValidity(input, true, apply);
+
+      input.step = 'AnY';
+      input.value = '0.1';
+      is(input.value, 0.1, "check that a positive fractional value with step=AnY is unchanged");
+      checkValidity(input, true, apply);
+
+      input.step = 'ANY';
+      input.min = -100;
+      input.value = '-13.37';
+      is(input.value, -13.37, "check that a negative fractional value with step=ANY is unchanged");
+      checkValidity(input, true, apply);
+
+      // When min is set to a valid float, there is a step base.
+      input.min = '1'; // the step base
+      input.step = '2';
+      input.value = '3';
+      is(input.value, 3, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '2';
+      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '1.99';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.removeAttribute('step'); // step = 1
+      input.min = '0.5'; // step base
+      input.value = '5.5';
+      is(input.value, 5.5, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '1';
+      is(input.value, 1.5, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.min = '-0.1'; // step base
+      input.step = '1';
+      input.value = '0.9';
+      is(input.value, 0.9, "the value should be a valid step");
+      checkValidity(input, true, apply);
+
+      input.value = '0.1';
+      is(input.value, -0.1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // When min is set to NaN, the step base is the value.
+      input.min = 'foo';
+      input.step = '1';
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '0.5';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.min = '';
+      input.value = '1';
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = '0.5';
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.removeAttribute('min');
+
+      // Test when the value isn't a number
+      input.value = '';
+      is(input.value, 50, "value be should default to the value midway between the minimum (0) and the maximum (100)");
+      checkValidity(input, true, apply);
+
+      // Regular situations.
+      input.step = '2';
+      input.value = '1.5';
+      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = '42.0';
+      is(input.value, 42, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.step = '0.1';
+      input.value = '-0.1';
+      is(input.value, 0, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.step = '2';
+      input.removeAttribute('min');
+      input.max = '10';
+      input.value = '-9';
+      is(input.value, 0, "check the value is clamped to the minimum's default of zero");
+      checkValidity(input, true, apply);
+
+      // If @value is defined but not @min, the step base is @value.
+      input = getFreshElement(test.type);
+      input.setAttribute('value', '1');
+      input.step = 2;
+      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      input.value = 3;
+      is(input.value, 3, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = 2;
+      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // Should also work with defaultValue.
+      input = getFreshElement(test.type);
+      input.defaultValue = 1;
+      input.step = 2;
+      is(input.value, 1, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = 3;
+      is(input.value, 3, "check that the value coincides with a step");
+      checkValidity(input, true, apply);
+
+      input.value = 2;
+      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      checkValidity(input, true, apply);
+
+      // Check contrived error case where there are no valid steps in range:
+      // No @min, so the step base is the default minimum, zero, the valid
+      // range is 0-1, -1 gets clamped to zero.
+      input = getFreshElement(test.type);
+      input.step = '3';
+      input.max = '1';
+      input.defaultValue = '-1';
+      is(input.value, 0, "the value should have been clamped to the default minimum, zero");
+      checkValidity(input, false, apply, {low: -1, high: -1});
+
+      // Check that when the closest of the two steps that the value is between
+      // is greater than the maximum we sanitize to the lower step.
+      input = getFreshElement(test.type);
+      input.step = '2';
+      input.min = '1';
+      input.max = '10.9';
+      input.value = '10.8'; // closest step in 11, but 11 > maximum
+      is(input.value, 9, "check that the value coincides with a step");
+
+      // The way that step base is defined, the converse (the value not being
+      // on a step, and the nearest step being a value that would be underflow)
+      // is not possible, so nothing to test there.
+
+      is(input.validationMessage, "",
+         "The validation message should be empty.");
+      break;
     case 'time':
       // Tests invalid step values. That defaults to step = 1 minute (60).
       var values = [ '0', '-1', 'foo', 'any', 'ANY', 'aNy' ];
       for (var value of values) {
         input.step = value;
         input.value = '19:06:00';
         checkValidity(input, true, apply);
         input.value = '19:06:51';
--- a/content/html/content/test/forms/test_stepup_stepdown.html
+++ b/content/html/content/test/forms/test_stepup_stepdown.html
@@ -42,30 +42,30 @@ function checkAvailability()
     ["checkbox", false],
     ["radio", false],
     ["file", false],
     ["submit", false],
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
+    ["range", true],
     ["date", true],
     ["time", true],
     // The next types have not been implemented but will fallback to "text"
     // which has the same value.
     ["color", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
     ["month", true],
     ["week", true],
     ["datetime-local", true],
-    ["range", true],
   ];
 
   var element = document.createElement("input");
   element.setAttribute('value', '0');
 
   for (data of testData) {
     var exceptionCaught = false;
     element.type = data[0];
@@ -175,16 +175,83 @@ function checkStepDown()
     // With step = 'any'.
     [ '0',  'any',  null,  null,  1,    null,   true ],
     [ '0',  'ANY',  null,  null,  1,    null,   true ],
     [ '0',  'AnY',  null,  null,  1,    null,   true ],
     [ '0',  'aNy',  null,  null,  1,    null,   true ],
     // With @value = step base.
     [ '1',  '2',    null,  null,  null, '-1',   false ],
   ]},
+  { type: 'range', data: [
+    // Regular case.
+    [ '1',   null,  null,  null,  null, '0',    false ],
+    // Argument testing.
+    [ '1',   null,  null,  null,  1,    '0',    false ],
+    [ '9',   null,  null,  null,  9,    '0',    false ],
+    [ '1',   null,  null,  null,  -1,   '2',    false ],
+    [ '1',   null,  null,  null,  0,    '1',    false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '1',   null,  null,  null,  1.1,  '0',    false ],
+    // With step values.
+    [ '1',  '0.5',  null,  null,  null, '0.5',  false ],
+    [ '1',  '0.25', null,  null,  4,    '0',    false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '1',  '0',    null,  null,  null, '0',    false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '1',  '-1',   null,  null,  null, '0',    false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '1',  'foo',  null,  null,  null, '0',    false ],
+    // Min values testing.
+    [ '1',  '1',    'foo', null,  null, '0',    false ],
+    [ '1',  null,   '-10', null,  null, '0',    false ],
+    [ '1',  null,   '0',   null,  null, '0',    false ],
+    [ '1',  null,   '10',  null,  null, '10',   false ],
+    [ '1',  null,   '2',   null,  null, '2',    false ],
+    [ '1',  null,   '1',   null,  null, '1',    false ],
+    // Max values testing.
+    [ '1',  '1',    null,  'foo', null, '0',    false ],
+    [ '1',  null,   null,  '10',  null, '0',    false ],
+    [ '1',  null,   null,  '0',   null, '0',    false ],
+    [ '1',  null,   null,  '-10', null, '0',    false ],
+    [ '1',  null,   null,  '1',   null, '0',    false ],
+    [ '5',  null,   null,  '3',   '3',  '0',    false ],
+    [ '5',  '2',    '-6',  '3',   '2',  '-2',   false ],
+    [ '-3', '5',    '-10', '-3',  null, '-10',  false ],
+    // Step mismatch.
+    [ '1',  '2',    '-2',  null,  null, '0',    false ],
+    [ '3',  '2',    '-2',  null,  null, '2',    false ],
+    [ '3',  '2',    '-2',  null,  '2',  '0',    false ],
+    [ '3',  '2',    '-2',  null,  '-2', '8',    false ],
+    [ '1',  '2',    '-6',  null,  null, '0',    false ],
+    [ '1',  '2',    '-2',  null,  null, '0',    false ],
+    [ '1',  '3',    '-6',  null,  null, '-3',   false ],
+    [ '2',  '3',    '-6',  null,  null, '0',    false ],
+    [ '2',  '3',    '1',   null,  null, '1',    false ],
+    [ '5',  '3',    '1',   null,  null, '1',    false ],
+    [ '3',  '2',    '-6',  null,  null, '2',    false ],
+    [ '5',  '2',    '-6',  null,  null, '4',    false ],
+    [ '6',  '2',   '1',    null,  null, '5',    false ],
+    [ '8',  '3',   '1',    null,  null, '4',    false ],
+    [ '9',  '2',   '-10',  null,  null, '8',    false ],
+    [ '7',  '3',   '-10',  null,  null, '5',    false ],
+    [ '-2', '3',   '-10',  null,  null, '-4',   false ],
+    // Clamping.
+    [ '0',  '2',    '-1',  null,  null, '-1',   false ],
+    [ '10', '2',    '0',   '4',   '10', '0',    false ],
+    [ '10', '2',    '0',   '4',   '5',  '0',    false ],
+    // value = "" (default will be 50).
+    [ '',   null,   null,  null,  null, '49',   false ],
+    // With step = 'any'.
+    [ '0',  'any',  null,  null,  1,    null,   true ],
+    [ '0',  'ANY',  null,  null,  1,    null,   true ],
+    [ '0',  'AnY',  null,  null,  1,    null,   true ],
+    [ '0',  'aNy',  null,  null,  1,    null,   true ],
+    // With @value = step base.
+    [ '1',  '2',    null,  null,  null, '1',    false ],
+  ]},
   { type: 'date', data: [
     // Regular case.
     [ '2012-07-09',  null,  null,  null,  null, '2012-07-08',   false ],
     // Argument testing.
     [ '2012-07-09',  null,  null,  null,  1,    '2012-07-08',   false ],
     [ '2012-07-09',  null,  null,  null,  5,    '2012-07-04',   false ],
     [ '2012-07-09',  null,  null,  null,  -1,   '2012-07-10',   false ],
     [ '2012-07-09',  null,  null,  null,  0,    '2012-07-09',   false ],
@@ -312,41 +379,45 @@ function checkStepDown()
   ]},
   ];
 
   for (var test of testData) {
     for (var data of test.data) {
       var element = document.createElement("input");
       element.type = test.type;
 
-      if (data[0] != null) {
-        element.setAttribute('value', data[0]);
-      }
-
       if (data[1] != null) {
         element.step = data[1];
       }
 
       if (data[2] != null) {
         element.min = data[2];
       }
 
       if (data[3] != null) {
         element.max = data[3];
       }
 
+      // Set 'value' last for type=range, because the final sanitized value
+      // after setting 'step', 'min' and 'max' can be affected by the order in
+      // which those attributes are set. Setting 'value' last makes it simpler
+      // to reason about what the final value should be.
+      if (data[0] != null) {
+        element.setAttribute('value', data[0]);
+      }
+
       var exceptionCaught = false;
       try {
         if (data[4] != null) {
           element.stepDown(data[4]);
         } else {
           element.stepDown();
         }
 
-        is(element.value, data[5], "The value should be " + data[5]);
+        is(element.value, data[5], "The value for type=" + test.type + " should be " + data[5]);
       } catch (e) {
         exceptionCaught = true;
         is(element.value, data[0], e.name + "The value should not have changed");
         is(e.name, 'InvalidStateError',
            "It should be a InvalidStateError exception.");
       } finally {
         is(exceptionCaught, data[6], "exception status should be " + data[6]);
       }
@@ -419,16 +490,80 @@ function checkStepUp()
     // With step = 'any'.
     [ '0',  'any',  null,  null,  1,    null,  true ],
     [ '0',  'ANY',  null,  null,  1,    null,  true ],
     [ '0',  'AnY',  null,  null,  1,    null,  true ],
     [ '0',  'aNy',  null,  null,  1,    null,  true ],
     // With @value = step base.
     [ '1',  '2',    null,  null,  null, '3',   false ],
   ]},
+  { type: 'range', data: [
+    // Regular case.
+    [ '1',   null,  null,  null,  null, '2',   false ],
+    // Argument testing.
+    [ '1',   null,  null,  null,  1,    '2',   false ],
+    [ '9',   null,  null,  null,  9,    '18',  false ],
+    [ '1',   null,  null,  null,  -1,   '0',   false ],
+    [ '1',   null,  null,  null,  0,    '1',   false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '1',   null,  null,  null,  1.1,  '2',   false ],
+    // With step values.
+    [ '1',  '0.5',  null,  null,  null, '1.5', false ],
+    [ '1',  '0.25', null,  null,  4,    '2',   false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '1',  '0',    null,  null,  null, '2',   false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '1',  '-1',   null,  null,  null, '2',   false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '1',  'foo',  null,  null,  null, '2',   false ],
+    // Min values testing.
+    [ '1',  '1',    'foo', null,  null, '2',   false ],
+    [ '1',  null,   '-10', null,  null, '2',   false ],
+    [ '1',  null,   '0',   null,  null, '2',   false ],
+    [ '1',  null,   '10',  null,  null, '11',  false ],
+    [ '1',  null,   '2',   null,  null, '3',   false ],
+    [ '1',  null,   '1',   null,  null, '2',   false ],
+    [ '0',  null,   '4',   null,  '5',  '9',   false ],
+    [ '0',  '2',    '5',   null,  '3',  '11',  false ],
+    // Max values testing.
+    [ '1',  '1',    null,  'foo', null, '2',   false ],
+    [ '1',  null,   null,  '10',  null, '2',   false ],
+    [ '1',  null,   null,  '0',   null, '0',   false ],
+    [ '1',  null,   null,  '-10', null, '0',   false ],
+    [ '1',  null,   null,  '1',   null, '1',   false ],
+    [ '-3', '5',    '-10', '-3',  null, '-5',  false ],
+    // Step mismatch.
+    [ '1',  '2',    '0',   null,  null, '4',   false ],
+    [ '1',  '2',    '0',   null,  '2',  '6',   false ],
+    [ '8',  '2',    null,  '9',   null, '8',   false ],
+    [ '-3', '2',    '-6',  null,  null, '0',   false ],
+    [ '9',  '3',    '-10', null,  null, '11',  false ],
+    [ '7',  '3',    '-10', null,  null, '11',  false ],
+    [ '7',  '3',    '5',   null,  null, '11',  false ],
+    [ '9',  '4',    '3',   null,  null, '15',  false ],
+    [ '-2', '3',    '-6',  null,  null, '0',   false ],
+    [ '7',  '3',    '6',   null,  null, '9',   false ],
+    // Clamping.
+    [ '1',  '2',    '0',  '3',   null,  '2',   false ],
+    [ '0',  '5',    '1',  '8',   '10',  '6',   false ],
+    [ '-9', '3',    '-8', '-1',  '5',   '-2',  false ],
+    [ '-9', '3',    '8',  '15',  '15',  '14',  false ],
+    [ '-1', '3',    '-1', '4',   '3',   '2',   false ],
+    [ '-3', '2',    '-6',  '-2',  null, '-2',  false ],
+    [ '-3', '2',    '-6',  '-1',  null, '-2',  false ],
+    // value = "" (default will be 50).
+    [ '',   null,   null,  null,  null, '51',  false ],
+    // With step = 'any'.
+    [ '0',  'any',  null,  null,  1,    null,  true ],
+    [ '0',  'ANY',  null,  null,  1,    null,  true ],
+    [ '0',  'AnY',  null,  null,  1,    null,  true ],
+    [ '0',  'aNy',  null,  null,  1,    null,  true ],
+    // With @value = step base.
+    [ '1',  '2',    null,  null,  null, '3',   false ],
+  ]},
   { type: 'date', data: [
     // Regular case.
     [ '2012-07-09',  null,  null,  null,  null, '2012-07-10',   false ],
     // Argument testing.
     [ '2012-07-09',  null,  null,  null,  1,    '2012-07-10',   false ],
     [ '2012-07-09',  null,  null,  null,  9,    '2012-07-18',   false ],
     [ '2012-07-09',  null,  null,  null,  -1,   '2012-07-08',   false ],
     [ '2012-07-09',  null,  null,  null,  0,    '2012-07-09',   false ],
@@ -559,41 +694,45 @@ function checkStepUp()
   ]},
   ];
 
   for (var test of testData) {
     for (var data of test.data) {
       var element = document.createElement("input");
       element.type = test.type;
 
-      if (data[0] != null) {
-        element.setAttribute('value', data[0]);
-      }
-
       if (data[1] != null) {
         element.step = data[1];
       }
 
       if (data[2] != null) {
         element.min = data[2];
       }
 
       if (data[3] != null) {
         element.max = data[3];
       }
 
+      // Set 'value' last for type=range, because the final sanitized value
+      // after setting 'step', 'min' and 'max' can be affected by the order in
+      // which those attributes are set. Setting 'value' last makes it simpler
+      // to reason about what the final value should be.
+      if (data[0] != null) {
+        element.setAttribute('value', data[0]);
+      }
+
       var exceptionCaught = false;
       try {
         if (data[4] != null) {
           element.stepUp(data[4]);
         } else {
           element.stepUp();
         }
 
-        is(element.value, data[5], "The value should be " + data[5]);
+        is(element.value, data[5], "The value for type=" + test.type + " should be " + data[5]);
       } catch (e) {
         exceptionCaught = true;
         is(element.value, data[0], e.name + "The value should not have changed");
         is(e.name, 'InvalidStateError',
            "It should be a InvalidStateError exception.");
       } finally {
         is(exceptionCaught, data[6], "exception status should be " + data[6]);
       }
--- a/content/html/content/test/forms/test_valueasdate_attribute.html
+++ b/content/html/content/test/forms/test_valueasdate_attribute.html
@@ -35,21 +35,21 @@ var validTypes =
   ["checkbox", false],
   ["radio", false],
   ["file", false],
   ["submit", false],
   ["image", false],
   ["reset", false],
   ["button", false],
   ["number", false],
+  ["range", false],
   ["date", true],
   ["time", true],
   // The next types have not been implemented but will fallback to "text"
   // which has the same value.
-  ["range", false],
   ["color", false],
 ];
 
 var todoTypes =
 [
   ["datetime", true],
   ["month", true],
   ["week", true],
--- a/content/html/content/test/forms/test_valueasnumber_attribute.html
+++ b/content/html/content/test/forms/test_valueasnumber_attribute.html
@@ -35,30 +35,30 @@ function checkAvailability()
     ["checkbox", false],
     ["radio", false],
     ["file", false],
     ["submit", false],
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
+    ["range", true],
     ["date", true],
     ["time", true],
     // The next types have not been implemented but will fallback to "text"
     // which has the same value.
     ["color", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
     ["month", true],
     ["week", true],
     ["datetime-local", true],
-    ["range", true],
   ];
 
   var element = document.createElement('input');
 
   for (data of testData) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
@@ -177,16 +177,107 @@ function checkNumberSet()
       ok(caught, "valueAsNumber should have thrown");
       is(element.value, data[1], "value should not have changed");
     } else {
       ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 }
 
+function checkRangeGet()
+{
+  // For type=range we should never get NaN since the user agent is required
+  // to fix up the input's value to be something sensible.
+
+  var min = -200;
+  var max = 200;
+  var defaultValue = min + (max - min)/2;
+
+  var testData =
+  [
+    ["42", 42],
+    ["-42", -42], // should work for negative values
+    ["42.1234", 42.1234],
+    ["123.12345678912345", 123.12345678912345], // double precision
+    ["1e2", 100], // e should be usable
+    ["2e1", 20],
+    ["1e-1", 0.1], // value after e can be negative
+    ["1E2", 100], // E can be used instead of e
+    ["e", defaultValue],
+    ["e2", defaultValue],
+    ["1e0.1", defaultValue],
+    ["", defaultValue],
+    ["foo", defaultValue],
+    ["42,13", defaultValue],
+  ];
+
+  var element = document.createElement('input');
+  element.type = "range";
+  element.setAttribute("min", min); // avoids out of range sanitization
+  element.setAttribute("max", max);
+  element.setAttribute("step", "any"); // avoids step mismatch sanitization
+  for (data of testData) {
+    element.value = data[0];
+
+    // Given that NaN != NaN, we have to use null when the expected value is NaN.
+    is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+       "floating point representation of the value");
+  }
+}
+
+function checkRangeSet()
+{
+  var min = -200;
+  var max = 200;
+  var defaultValue = min + (max - min)/2;
+
+  var testData =
+  [
+    [42, "42"],
+    [-42, "-42"], // should work for negative values
+    [42.1234, "42.1234"],
+    [123.123456789123, "123.123456789123"], // double precision
+    [1e2, "100"], // e should be usable
+    [2e1, "20"],
+    [1e-1, "0.1"], // value after e can be negative
+    [1E2, "100"], // E can be used instead of e
+    ["foo", defaultValue],
+    ["", defaultValue],
+    [42, "42"], // Keep this here, it is used by the next test.
+    // Setting Infinity should throw and not change the current value.
+    [Infinity, "42", true],
+    [-Infinity, "42", true],
+    // Setting NaN should change the value to the empty string.
+    [NaN, defaultValue],
+  ];
+
+  var element = document.createElement('input');
+  element.type = "range";
+  element.setAttribute("min", min); // avoids out of range sanitization
+  element.setAttribute("max", max);
+  element.setAttribute("step", "any"); // avoids step mismatch sanitization
+  for (data of testData) {
+    var caught = false;
+    try {
+      element.valueAsNumber = data[0];
+      is(element.value, data[1],
+         "valueAsNumber should be able to set the value");
+    } catch (e) {
+      caught = true;
+    }
+
+    if (data[2]) {
+      ok(caught, "valueAsNumber should have thrown");
+      is(element.value, data[1], "value should not have changed");
+    } else {
+      ok(!caught, "valueAsNumber should not have thrown");
+    }
+  }
+}
+
 function checkDateGet()
 {
   var validData =
   [
     [ "2012-07-12", 1342051200000 ],
     [ "1970-01-01", 0 ],
     // We are supposed to support at least until this date.
     // (corresponding to the date object maximal value)
@@ -397,16 +488,20 @@ function checkTimeSet()
 }
 
 checkAvailability();
 
 // <input type='number'> test
 checkNumberGet();
 checkNumberSet();
 
+// <input type='range'> test
+checkRangeGet();
+checkRangeSet();
+
 // <input type='date'> test
 checkDateGet();
 checkDateSet();
 
 // <input type='time'> test
 checkTimeGet();
 checkTimeSet();
 
--- a/content/html/content/test/test_bug590363.html
+++ b/content/html/content/test/test_bug590363.html
@@ -32,34 +32,44 @@ var testData = [
   [ "text",     true ],
   [ "url",      true ],
   [ "email",    true ],
   [ "search",   true ],
   [ "password", true ],
   [ "number",   true ],
   [ "date",     true ],
   [ "time",     true ],
+  [ "range",    true ],
   // 'file' is treated separatly.
 ];
 
 var todoTypes = [
-  "datetime", "month", "week", "datetime-local", "range", "color"
+  "datetime", "month", "week", "datetime-local", "color"
 ];
 
 var nonTrivialSanitizing = [ 'number', 'date', 'time' ];
 
 var length = testData.length;
 for (var i=0; i<length; ++i) {
   for (var j=0; j<length; ++j) {
     var e = document.createElement('input');
     e.type = testData[i][0];
 
     var expectedValue;
-    if (nonTrivialSanitizing.indexOf(testData[i][0]) != -1 &&
-        nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
+
+    // range will sanitize its value to 50 (the default) if it isn't a valid
+    // number. We need to handle that specially.
+    if (testData[j][0] == 'range' || testData[i][0] == 'range') {
+      if (testData[j][0] == 'date' || testData[j][0] == 'time') {
+        expectedValue = '';
+      } else {
+        expectedValue = '50';
+      }
+    } else if (nonTrivialSanitizing.indexOf(testData[i][0]) != -1 &&
+               nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
       expectedValue = '';
     } else if (testData[i][0] == 'number' || testData[j][0] == 'number') {
       expectedValue = '42';
     } else if (testData[i][0] == 'date' || testData[j][0] == 'date') {
       expectedValue = '2012-12-21';
     } else if (testData[i][0] == 'time' || testData[j][0] == 'time') {
       expectedValue = '21:21';
     } else {
@@ -77,17 +87,20 @@ for (var i=0; i<length; ++i) {
 // We are just going to check that we do not loose the value.
 for (var data of testData) {
   var e = document.createElement('input');
   e.type = data[0];
   e.value = 'foo';
   e.type = 'file';
   e.type = data[0];
 
-  if (data[1]) {
+  if (data[0] == 'range') {
+    is(e.value, '50', ".value should still return the same value after " +
+       "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+  } else if (data[1]) {
     is(e.value, '', ".value should have been reset to the empty string after " +
        "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
   } else {
     is(e.value, 'foo', ".value should still return the same value after " +
        "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
   }
 }
 
--- a/content/html/content/test/test_bug598643.html
+++ b/content/html/content/test/test_bug598643.html
@@ -45,19 +45,19 @@ function testFileControl(aElement)
      "the file control shouldn't suffer from being too long");
 }
 
 var types = [
   // These types can be too long.
   [ "text", "email", "password", "url", "search", "tel" ],
   // These types can't be too long.
   [ "radio", "checkbox", "submit", "button", "reset", "image", "hidden",
-    'number', 'date', 'time' ],
+    'number', 'range', 'date', 'time' ],
   // These types can't be too long but are not implemented yet.
-  [ "range", "color", "datetime", "month", "week", 'datetime-local' ]
+  [ "color", "datetime", "month", "week", 'datetime-local' ]
 ];
 
 var input = document.createElement("input");
 input.maxLength = 1;
 input.value = "foo";
 
 // Too long types.
 for (type of types[0]) {
--- a/toolkit/components/passwordmgr/test/test_basic_form_html5.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_html5.html
@@ -148,17 +148,17 @@ function startTest() {
   is($_(8, "uname").value, "", "type=month should not be considered a username");
   
   is($_(9, "uname").value, "", "type=week should not be considered a username");
   
   is($_(10, "uname").value, "", "type=time should not be considered a username");
   
   is($_(11, "uname").value, "", "type=datetime-local should not be considered a username");
   
-  is($_(12, "uname").value, "", "type=range should not be considered a username");
+  is($_(12, "uname").value, "50", "type=range should not be considered a username");
   
   is($_(13, "uname").value, "", "type=color should not be considered a username");
 
   pwmgr.removeLogin(login1);
   pwmgr.removeLogin(login2);
   pwmgr.removeLogin(login3);
   pwmgr.removeLogin(login4);
  
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -99,16 +99,21 @@ Form History test: form field autocomple
   </form>
 
   <!-- form with input type='time' -->
   <form id="form15" onsubmit="return false;">
     <input  type="time" name="field12">
     <button type="submit">Submit</button>
   </form>
 
+  <!-- form with input type='range' -->
+  <form id="form16" onsubmit="return false;">
+    <input  type="range" name="field13" max="64">
+    <button type="submit">Submit</button>
+  </form>
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Form History autocomplete **/
 
@@ -144,20 +149,21 @@ fh.addEntry("field5", "123");
 fh.addEntry("field5", "1234");
 fh.addEntry("field6", "value");
 fh.addEntry("field7", "value");
 fh.addEntry("field8", "value");
 fh.addEntry("field9", "value");
 fh.addEntry("field10", "42");
 fh.addEntry("field11", "2010-10-10");
 fh.addEntry("field12", "21:21");
+fh.addEntry("field13", "32"); // not used, since type=range doesn't have a drop down menu
 fh.addEntry("searchbar-history", "blacklist test");
 
 // All these non-implemeted types might need autocomplete tests in the future.
-var todoTypes = [ "datetime", "month", "week", "datetime-local", "range", "color" ];
+var todoTypes = [ "datetime", "month", "week", "datetime-local", "color" ];
 var todoInput = document.createElement("input");
 for (var type of todoTypes) {
   todoInput.type = type;
   todo_is(todoInput.type, type, type + " type shouldn't be implemented");
 }
 
 
 function setForm(value) {
@@ -761,16 +767,27 @@ function runTest(testNum) {
         break;
 
     case 406:
         checkMenuEntries(["21:21"]);
         doKey("down");
         doKey("return");
         checkForm("21:21");
 
+        input = $_(16, "field13");
+        restoreForm();
+        doKey("down");
+        break;
+
+      case 407:
+        checkMenuEntries([]); // type=range does not have a drop down menu
+        doKey("down");
+        doKey("return");
+        checkForm("32"); // default (midway between minimum (0) and maximum (64))
+
         // Go to test 500.
         fh.addEntry("field1", "value1");
         input = $_(1, "field1");
         testNum = 499;
 
         restoreForm();
         doKey("down");
         break;