author | Jonathan Watt <jwatt@jwatt.org> |
Sat, 16 Feb 2013 12:36:02 +0000 | |
changeset 122130 | fb7e4cc681084ad81d3396a2a588c19f63a4c635 |
parent 122129 | 125640d9063135da06af7039d235edae1580c0e9 |
child 122131 | 2eba5f66565a6d476f7448257007de04a9bd8047 |
push id | 24319 |
push user | ryanvm@gmail.com |
push date | Sat, 16 Feb 2013 23:49:46 +0000 |
treeherder | mozilla-central@c4de6de47382 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mounir |
bugs | 836323 |
milestone | 21.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
|
--- 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;