From dc7188dcc28ec82b2e9fd91a40a3c01a2e9aa9c0 Mon Sep 17 00:00:00 2001 From: Jonathan Watt Date: Thu, 30 Jan 2014 12:54:12 +0000 Subject: [PATCH] Bug 827161, part 2 - Implement HTML 5's ValidityState.badInput and implement the state for HTMLInputElement's number type. r=smaug --- .../content/public/nsIConstraintValidation.h | 24 +-- content/html/content/src/HTMLInputElement.cpp | 172 ++++++++++++++---- content/html/content/src/HTMLInputElement.h | 2 + content/html/content/src/ValidityState.cpp | 7 + content/html/content/src/ValidityState.h | 4 + .../content/src/nsIConstraintValidation.cpp | 2 + content/html/content/test/forms/mochitest.ini | 4 + .../test/forms/test_input_number_data.js | 23 +++ .../test/forms/test_input_number_l10n.html | 24 +-- .../forms/test_input_number_validation.html | 99 ++++++++++ .../forms/test_input_typing_sanitization.html | 33 +++- .../content/test/forms/test_validation.html | 8 + dom/interfaces/html/nsIDOMValidityState.idl | 3 +- dom/locales/en-US/chrome/dom/dom.properties | 1 + dom/webidl/ValidityState.webidl | 2 +- layout/forms/nsNumberControlFrame.cpp | 11 ++ layout/forms/nsNumberControlFrame.h | 2 + .../en-US/webapprt/overrides/dom.properties | 1 + 18 files changed, 340 insertions(+), 82 deletions(-) create mode 100644 content/html/content/test/forms/test_input_number_data.js create mode 100644 content/html/content/test/forms/test_input_number_validation.html diff --git a/content/html/content/public/nsIConstraintValidation.h b/content/html/content/public/nsIConstraintValidation.h index 580f798155a1..6e565bd365b2 100644 --- a/content/html/content/public/nsIConstraintValidation.h +++ b/content/html/content/public/nsIConstraintValidation.h @@ -19,8 +19,8 @@ class ValidityState; } #define NS_ICONSTRAINTVALIDATION_IID \ -{ 0xca3824dc, 0x4f5c, 0x4878, \ - { 0xa6, 0x8a, 0x95, 0x54, 0x5f, 0xfa, 0x4b, 0xf9 } } +{ 0x983829da, 0x1aaf, 0x449c, \ + { 0xa3, 0x06, 0x85, 0xd4, 0xf0, 0x31, 0x1c, 0xf6 } } /** * This interface is for form elements implementing the validity constraint API. @@ -51,14 +51,16 @@ public: enum ValidityStateType { - VALIDITY_STATE_VALUE_MISSING = 0x01, // 0b00000001 - VALIDITY_STATE_TYPE_MISMATCH = 0x02, // 0b00000010 - VALIDITY_STATE_PATTERN_MISMATCH = 0x04, // 0b00000100 - VALIDITY_STATE_TOO_LONG = 0x08, // 0b00001000 - VALIDITY_STATE_RANGE_UNDERFLOW = 0x10, // 0b00010000 - VALIDITY_STATE_RANGE_OVERFLOW = 0x20, // 0b00100000 - VALIDITY_STATE_STEP_MISMATCH = 0x40, // 0b01000000 - VALIDITY_STATE_CUSTOM_ERROR = 0x80 // 0b10000000 + VALIDITY_STATE_VALUE_MISSING = 0x1 << 0, + VALIDITY_STATE_TYPE_MISMATCH = 0x1 << 1, + VALIDITY_STATE_PATTERN_MISMATCH = 0x1 << 2, + VALIDITY_STATE_TOO_LONG = 0x1 << 3, + //VALIDITY_STATE_TOO_SHORT = 0x1 << 4, + VALIDITY_STATE_RANGE_UNDERFLOW = 0x1 << 5, + VALIDITY_STATE_RANGE_OVERFLOW = 0x1 << 6, + VALIDITY_STATE_STEP_MISMATCH = 0x1 << 7, + VALIDITY_STATE_BAD_INPUT = 0x1 << 8, + VALIDITY_STATE_CUSTOM_ERROR = 0x1 << 9, }; void SetValidityState(ValidityStateType mState, @@ -103,7 +105,7 @@ private: * A bitfield representing the current validity state of the element. * Each bit represent an error. All bits to zero means the element is valid. */ - int8_t mValidityBitField; + int16_t mValidityBitField; /** * Keeps track whether the element is barred from constraint validation. diff --git a/content/html/content/src/HTMLInputElement.cpp b/content/html/content/src/HTMLInputElement.cpp index bd91e4768ad5..7c7e9eb7eb87 100644 --- a/content/html/content/src/HTMLInputElement.cpp +++ b/content/html/content/src/HTMLInputElement.cpp @@ -2767,6 +2767,7 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, if (!mParserCreating) { SanitizeValue(value); } + // else SanitizeValue will be called by DoneCreatingElement if (aSetValueChanged) { SetValueChanged(true); @@ -2774,6 +2775,9 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, if (IsSingleLineTextControl(false)) { mInputData.mState->SetValue(value, aUserInput, aSetValueChanged); + if (mType == NS_FORM_INPUT_EMAIL) { + UpdateAllValidityStates(mParserCreating); + } } else { mInputData.mValue = ToNewUnicode(value); if (aSetValueChanged) { @@ -3471,7 +3475,7 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor) numberControlFrame->GetValueOfAnonTextControl(value); numberControlFrame->HandlingInputEvent(true); nsWeakFrame weakNumberControlFrame(numberControlFrame); - SetValueInternal(value, false, true); + SetValueInternal(value, true, true); if (weakNumberControlFrame.IsAlive()) { numberControlFrame->HandlingInputEvent(false); } @@ -6422,6 +6426,103 @@ HTMLInputElement::HasStepMismatch() const return NS_floorModulo(value - GetStepBase(), step) != 0; } +/** + * Splits the string on the first "@" character and punycode encodes the first + * and second parts separately before rejoining them with an "@" and returning + * the result via the aEncodedEmail out-param. Returns false if there is no + * "@" caracter, if the "@" character is at the start or end, or if the + * conversion to punycode fails. + * + * This function exists because ConvertUTF8toACE() treats 'username@domain' as + * a single label, but we need to encode the username and domain parts + * separately. + */ +static bool PunycodeEncodeEmailAddress(const nsAString& aEmail, + nsAutoCString& aEncodedEmail, + uint32_t* aIndexOfAt) +{ + nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail); + uint32_t length = value.Length(); + + uint32_t atPos = (uint32_t)value.FindChar('@'); + // Email addresses must contain a '@', but can't begin or end with it. + if (atPos == (uint32_t)kNotFound || atPos == 0 || atPos == length - 1) { + return false; + } + + nsCOMPtr idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID); + if (!idnSrv) { + NS_ERROR("nsIIDNService isn't present!"); + return false; + } + + const nsDependentCSubstring username = Substring(value, 0, atPos); + bool ace; + if (NS_SUCCEEDED(idnSrv->IsACE(username, &ace)) && !ace) { + nsAutoCString usernameACE; + // TODO: Bug 901347: Usernames longer than 63 chars are not converted by + // ConvertUTF8toACE(). For now, continue on even if the conversion fails. + if (NS_SUCCEEDED(idnSrv->ConvertUTF8toACE(username, usernameACE))) { + value.Replace(0, atPos, usernameACE); + atPos = usernameACE.Length(); + } + } + + const nsDependentCSubstring domain = Substring(value, atPos + 1); + if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) { + nsAutoCString domainACE; + if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) { + return false; + } + value.Replace(atPos + 1, domain.Length(), domainACE); + } + + aEncodedEmail = value; + *aIndexOfAt = atPos; + return true; +} + +bool +HTMLInputElement::HasBadInput() const +{ + if (mType == NS_FORM_INPUT_NUMBER) { + nsAutoString value; + GetValueInternal(value); + if (!value.IsEmpty()) { + // The input can't be bad, otherwise it would have been sanitized to the + // empty string. + NS_ASSERTION(!GetValueAsDecimal().isNaN(), "Should have sanitized"); + return false; + } + nsNumberControlFrame* numberControlFrame = + do_QueryFrame(GetPrimaryFrame()); + if (numberControlFrame && + !numberControlFrame->AnonTextControlIsEmpty()) { + // The input the user entered failed to parse as a number. + return true; + } + return false; + } + if (mType == NS_FORM_INPUT_EMAIL) { + // With regards to suffering from bad input the spec says that only the + // punycode conversion works, so we don't care whether the email address is + // valid or not here. (If the email address is invalid then we will be + // suffering from a type mismatch.) + nsAutoString value; + nsAutoCString unused; + uint32_t unused2; + NS_ENSURE_SUCCESS(GetValueInternal(value), false); + HTMLSplitOnSpacesTokenizer tokenizer(value, ','); + while (tokenizer.hasMoreTokens()) { + if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) { + return true; + } + } + return false; + } + return false; +} + void HTMLInputElement::UpdateTooLongValidityState() { @@ -6518,6 +6619,12 @@ HTMLInputElement::UpdateStepMismatchValidityState() SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch()); } +void +HTMLInputElement::UpdateBadInputValidityState() +{ + SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput()); +} + void HTMLInputElement::UpdateAllValidityStates(bool aNotify) { @@ -6529,6 +6636,7 @@ HTMLInputElement::UpdateAllValidityStates(bool aNotify) UpdateRangeOverflowValidityState(); UpdateRangeUnderflowValidityState(); UpdateStepMismatchValidityState(); + UpdateBadInputValidityState(); if (validBefore != IsValid()) { UpdateState(aNotify); @@ -6753,6 +6861,22 @@ HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage, aValidationMessage = message; break; } + case VALIDITY_STATE_BAD_INPUT: + { + nsXPIDLString message; + nsAutoCString key; + if (mType == NS_FORM_INPUT_NUMBER) { + key.AssignLiteral("FormValidationBadInputNumber"); + } else if (mType == NS_FORM_INPUT_EMAIL) { + key.AssignLiteral("FormValidationInvalidEmail"); + } else { + return NS_ERROR_UNEXPECTED; + } + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + key.get(), message); + aValidationMessage = message; + break; + } default: rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType); } @@ -6779,51 +6903,25 @@ HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue) bool HTMLInputElement::IsValidEmailAddress(const nsAString& aValue) { - nsAutoCString value = NS_ConvertUTF16toUTF8(aValue); - uint32_t i = 0; - uint32_t length = value.Length(); - // Email addresses can't be empty and can't end with a '.' or '-'. - if (length == 0 || value[length - 1] == '.' || value[length - 1] == '-') { + if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') { return false; } - uint32_t atPos = (uint32_t)value.FindChar('@'); + uint32_t atPos; + nsAutoCString value; + if (!PunycodeEncodeEmailAddress(aValue, value, &atPos)) { + return false; + } + + uint32_t length = value.Length(); + // Email addresses must contain a '@', but can't begin or end with it. if (atPos == (uint32_t)kNotFound || atPos == 0 || atPos == length - 1) { return false; } - // Puny-encode the string if needed before running the validation algorithm. - nsCOMPtr idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID); - if (idnSrv) { - // ConvertUTF8toACE() treats 'username@domain' as a single label so we need - // to puny-encode the username and domain parts separately. - const nsDependentCSubstring username = Substring(value, 0, atPos); - bool ace; - if (NS_SUCCEEDED(idnSrv->IsACE(username, &ace)) && !ace) { - nsAutoCString usernameACE; - // TODO: Bug 901347: Usernames longer than 63 chars are not converted by - // ConvertUTF8toACE(). For now, continue on even if the conversion fails. - if (NS_SUCCEEDED(idnSrv->ConvertUTF8toACE(username, usernameACE))) { - value.Replace(0, atPos, usernameACE); - atPos = usernameACE.Length(); - } - } - - const nsDependentCSubstring domain = Substring(value, atPos + 1); - if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) { - nsAutoCString domainACE; - if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) { - return false; - } - value.Replace(atPos + 1, domain.Length(), domainACE); - } - - length = value.Length(); - } else { - NS_ERROR("nsIIDNService isn't present!"); - } + uint32_t i = 0; // Parsing the username. for (; i < atPos; ++i) { diff --git a/content/html/content/src/HTMLInputElement.h b/content/html/content/src/HTMLInputElement.h index a3499bcecb67..ad7f6897b069 100644 --- a/content/html/content/src/HTMLInputElement.h +++ b/content/html/content/src/HTMLInputElement.h @@ -257,6 +257,7 @@ public: bool IsRangeOverflow() const; bool IsRangeUnderflow() const; bool HasStepMismatch() const; + bool HasBadInput() const; void UpdateTooLongValidityState(); void UpdateValueMissingValidityState(); void UpdateTypeMismatchValidityState(); @@ -264,6 +265,7 @@ public: void UpdateRangeOverflowValidityState(); void UpdateRangeUnderflowValidityState(); void UpdateStepMismatchValidityState(); + void UpdateBadInputValidityState(); void UpdateAllValidityStates(bool aNotify); void UpdateBarredFromConstraintValidation(); nsresult GetValidationMessage(nsAString& aValidationMessage, diff --git a/content/html/content/src/ValidityState.cpp b/content/html/content/src/ValidityState.cpp index d2fbf60c01ff..e2876140d46c 100644 --- a/content/html/content/src/ValidityState.cpp +++ b/content/html/content/src/ValidityState.cpp @@ -76,6 +76,13 @@ ValidityState::GetStepMismatch(bool* aStepMismatch) return NS_OK; } +NS_IMETHODIMP +ValidityState::GetBadInput(bool* aBadInput) +{ + *aBadInput = BadInput(); + return NS_OK; +} + NS_IMETHODIMP ValidityState::GetCustomError(bool* aCustomError) { diff --git a/content/html/content/src/ValidityState.h b/content/html/content/src/ValidityState.h index 28711be86b39..5f48ef26455a 100644 --- a/content/html/content/src/ValidityState.h +++ b/content/html/content/src/ValidityState.h @@ -60,6 +60,10 @@ public: { return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_STEP_MISMATCH); } + bool BadInput() const + { + return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_BAD_INPUT); + } bool CustomError() const { return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_CUSTOM_ERROR); diff --git a/content/html/content/src/nsIConstraintValidation.cpp b/content/html/content/src/nsIConstraintValidation.cpp index ddfceacffbd8..523de8d2c46b 100644 --- a/content/html/content/src/nsIConstraintValidation.cpp +++ b/content/html/content/src/nsIConstraintValidation.cpp @@ -86,6 +86,8 @@ nsIConstraintValidation::GetValidationMessage(nsAString& aValidationMessage) GetValidationMessage(aValidationMessage, VALIDITY_STATE_RANGE_UNDERFLOW); } else if (GetValidityState(VALIDITY_STATE_STEP_MISMATCH)) { GetValidationMessage(aValidationMessage, VALIDITY_STATE_STEP_MISMATCH); + } else if (GetValidityState(VALIDITY_STATE_BAD_INPUT)) { + GetValidationMessage(aValidationMessage, VALIDITY_STATE_BAD_INPUT); } else { // There should not be other validity states. return NS_ERROR_UNEXPECTED; diff --git a/content/html/content/test/forms/mochitest.ini b/content/html/content/test/forms/mochitest.ini index a931f19f1c2c..7c438cb44132 100644 --- a/content/html/content/test/forms/mochitest.ini +++ b/content/html/content/test/forms/mochitest.ini @@ -2,6 +2,7 @@ support-files = save_restore_radio_groups.sjs submit_invalid_file.sjs + test_input_number_data.js [test_button_attributes_reflection.html] [test_change_event.html] @@ -32,6 +33,9 @@ skip-if = os == "android" || appname == "b2g" skip-if = os == "android" || appname == "b2g" [test_input_number_rounding.html] skip-if = os == "android" +[test_input_number_validation.html] +# We don't build ICU for Firefox for Android or Firefox OS: +skip-if = os == "android" || appname == "b2g" [test_input_range_attr_order.html] [test_input_range_key_events.html] [test_input_range_mouse_and_touch_events.html] diff --git a/content/html/content/test/forms/test_input_number_data.js b/content/html/content/test/forms/test_input_number_data.js new file mode 100644 index 000000000000..2fa6cf2d2bac --- /dev/null +++ b/content/html/content/test/forms/test_input_number_data.js @@ -0,0 +1,23 @@ + +var tests = [ + { desc: "British English", + langTag: "en-GB", inputWithGrouping: "123,456.78", + inputWithoutGrouping: "123456.78", value: 123456.78 + }, + { desc: "Farsi", + langTag: "fa", inputWithGrouping: "۱۲۳٬۴۵۶٫۷۸", + inputWithoutGrouping: "۱۲۳۴۵۶٫۷۸", value: 123456.78 + }, + { desc: "French", + langTag: "fr-FR", inputWithGrouping: "123 456,78", + inputWithoutGrouping: "123456,78", value: 123456.78 + }, + { desc: "German", + langTag: "de", inputWithGrouping: "123.456,78", + inputWithoutGrouping: "123456,78", value: 123456.78 + }, + { desc: "Hebrew", + langTag: "he", inputWithGrouping: "123,456.78", + inputWithoutGrouping: "123456.78", value: 123456.78 + }, +]; diff --git a/content/html/content/test/forms/test_input_number_l10n.html b/content/html/content/test/forms/test_input_number_l10n.html index f398110f00e1..0b1c875e69d1 100644 --- a/content/html/content/test/forms/test_input_number_l10n.html +++ b/content/html/content/test/forms/test_input_number_l10n.html @@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=844744 Test localization of number control input + @@ -31,29 +32,6 @@ SimpleTest.waitForFocus(function() { SimpleTest.finish(); }); -var tests = [ - { desc: "British English", - langTag: "en-GB", inputWithGrouping: "123,456.78", - inputWithoutGrouping: "123456.78", value: 123456.78 - }, - { desc: "Farsi", - langTag: "fa", inputWithGrouping: "۱۲۳٬۴۵۶٫۷۸", - inputWithoutGrouping: "۱۲۳۴۵۶٫۷۸", value: 123456.78 - }, - { desc: "French", - langTag: "fr-FR", inputWithGrouping: "123 456,78", - inputWithoutGrouping: "123456,78", value: 123456.78 - }, - { desc: "German", - langTag: "de", inputWithGrouping: "123.456,78", - inputWithoutGrouping: "123456,78", value: 123456.78 - }, - { desc: "Hebrew", - langTag: "he", inputWithGrouping: "123,456.78", - inputWithoutGrouping: "123456.78", value: 123456.78 - }, -]; - var elem; function runTest(test) { diff --git a/content/html/content/test/forms/test_input_number_validation.html b/content/html/content/test/forms/test_input_number_validation.html new file mode 100644 index 000000000000..29e04810b28c --- /dev/null +++ b/content/html/content/test/forms/test_input_number_validation.html @@ -0,0 +1,99 @@ + + + + + Test validation of number control input + + + + + + + +Mozilla Bug 827161 +

+
+ +
+
+
+
+ + diff --git a/content/html/content/test/forms/test_input_typing_sanitization.html b/content/html/content/test/forms/test_input_typing_sanitization.html index 20d2f6cd6aaf..e05647c8c001 100644 --- a/content/html/content/test/forms/test_input_typing_sanitization.html +++ b/content/html/content/test/forms/test_input_typing_sanitization.html @@ -31,6 +31,7 @@ var input = document.getElementById('i'); var form = document.getElementById('f'); var submitFrame = document.getElementsByTagName('iframe')[0]; var testData = []; +var gCurrentTest = null; var gValidData = []; var gInvalidData = []; @@ -46,6 +47,19 @@ function urlify(aStr) { return aStr.replace(':', '%3A', 'g'); } +function runTestsForNextInputType() +{ + try { + testRunner.next(); + } catch (e) { + if (e.toString() == '[object StopIteration]') { + SimpleTest.finish(); + } else { + throw StopIteration; + } + } +} + function checkValueSubmittedIsValid() { is(frames['submit_frame'].location.href, @@ -56,6 +70,12 @@ function checkValueSubmittedIsValid() input.value = ""; if (valueIndex >= gValidData.length) { + if (gCurrentTest.canHaveBadInputValidityState) { + // Don't run the submission tests on the invalid input if submission + // will be blocked by invalid input. + runTestsForNextInputType(); + return; + } valueIndex = 0; submitFrame.onload = checkValueSubmittedIsInvalid; testData = gInvalidData; @@ -74,15 +94,7 @@ function checkValueSubmittedIsInvalid() if (valueIndex >= gInvalidData.length) { if (submitMethod == sendKeyEventToSubmitForm) { - try { - testRunner.next(); - } catch (e) { - if (e.toString() == '[object StopIteration]') { - SimpleTest.finish(); - } else { - throw StopIteration; - } - } + runTestsForNextInputType(); return; } valueIndex = 0; @@ -109,6 +121,7 @@ function runTest() var data = [ { type: 'number', + canHaveBadInputValidityState: true, validData: [ "42", "-42", // should work for negative values @@ -173,6 +186,8 @@ function runTest() ]; for (test of data) { + gCurrentTest = test; + if (test.todo) { input.type = test.type; todo_is(input.type, test.type, test.type + " is not implemented"); diff --git a/content/html/content/test/forms/test_validation.html b/content/html/content/test/forms/test_validation.html index 0e394aab9fb4..b22274224930 100644 --- a/content/html/content/test/forms/test_validation.html +++ b/content/html/content/test/forms/test_validation.html @@ -57,6 +57,7 @@ function checkConstraintValidationAPIExist(element) validity = element.validity; ok('valueMissing' in validity, "validity.valueMissing is not available in the DOM"); ok('typeMismatch' in validity, "validity.typeMismatch is not available in the DOM"); + ok('badInput' in validity, "validity.badInput is not available in the DOM"); ok('patternMismatch' in validity, "validity.patternMismatch is not available in the DOM"); ok('tooLong' in validity, "validity.tooLong is not available in the DOM"); ok('rangeUnderflow' in validity, "validity.rangeUnderflow is not available in the DOM"); @@ -75,6 +76,7 @@ function checkConstraintValidationAPIDefaultValues(element) ok(!element.validity.valueMissing, "The element should not suffer from a constraint validation"); ok(!element.validity.typeMismatch, "The element should not suffer from a constraint validation"); + ok(!element.validity.badInput, "The element should not suffer from a constraint validation"); ok(!element.validity.patternMismatch, "The element should not suffer from a constraint validation"); ok(!element.validity.tooLong, "The element should not suffer from a constraint validation"); ok(!element.validity.rangeUnderflow, "The element should not suffer from a constraint validation"); @@ -152,6 +154,10 @@ function checkSpecificWillValidate() ok(!i.willValidate, "Submit state input should be barred from constraint validation"); is(window.getComputedStyle(i, null).getPropertyValue('background-color'), "rgb(0, 0, 0)", "Nor :valid and :invalid should apply"); + i.type = "number"; + ok(i.willValidate, "Number state input should not be barred from constraint validation"); + is(window.getComputedStyle(i, null).getPropertyValue('background-color'), + "rgb(0, 255, 0)", ":valid pseudo-class should apply"); i.type = ""; i.readOnly = 'true'; ok(!i.willValidate, "Readonly input should be barred from constraint validation"); @@ -275,6 +281,8 @@ function checkValidityStateObjectAliveWithoutElement(element) "When the element is not alive, it shouldn't suffer from constraint validation"); ok(!v.typeMismatch, "When the element is not alive, it shouldn't suffer from constraint validation"); + ok(!v.badInput, + "When the element is not alive, it shouldn't suffer from constraint validation"); ok(!v.patternMismatch, "When the element is not alive, it shouldn't suffer from constraint validation"); ok(!v.tooLong, diff --git a/dom/interfaces/html/nsIDOMValidityState.idl b/dom/interfaces/html/nsIDOMValidityState.idl index c7f1cad4d8a0..cd91a7165f05 100644 --- a/dom/interfaces/html/nsIDOMValidityState.idl +++ b/dom/interfaces/html/nsIDOMValidityState.idl @@ -13,7 +13,7 @@ * http://www.whatwg.org/specs/web-apps/current-work/#validitystate */ -[scriptable, uuid(5e62197a-9b74-4812-b5a2-ca102e886f7a)] +[scriptable, uuid(00bed276-f1f7-492f-a039-dbd9b9efc10b)] interface nsIDOMValidityState : nsISupports { readonly attribute boolean valueMissing; @@ -23,6 +23,7 @@ interface nsIDOMValidityState : nsISupports readonly attribute boolean rangeUnderflow; readonly attribute boolean rangeOverflow; readonly attribute boolean stepMismatch; + readonly attribute boolean badInput; readonly attribute boolean customError; readonly attribute boolean valid; }; diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 91f361718bab..90dbf837e729 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -41,6 +41,7 @@ FormValidationRangeUnderflow=Please select a value that is higher than %S. FormValidationStepMismatch=Please select a valid value. The two nearest valid values are %S and %S. # LOCALIZATION NOTE (FormValidationStepMismatchOneValue): %S can be a number, a date or a time. This is called instead of FormValidationStepMismatch when the second value is the same as the first. FormValidationStepMismatchOneValue=Please select a valid value. The nearest valid value is %S. +FormValidationBadInputNumber=Please enter a number. GetAttributeNodeWarning=Use of getAttributeNode() is deprecated. Use getAttribute() instead. SetAttributeNodeWarning=Use of setAttributeNode() is deprecated. Use setAttribute() instead. GetAttributeNodeNSWarning=Use of getAttributeNodeNS() is deprecated. Use getAttributeNS() instead. diff --git a/dom/webidl/ValidityState.webidl b/dom/webidl/ValidityState.webidl index d8d1d1bccd8d..a4cf8f198aa8 100644 --- a/dom/webidl/ValidityState.webidl +++ b/dom/webidl/ValidityState.webidl @@ -18,7 +18,7 @@ interface ValidityState { readonly attribute boolean rangeUnderflow; readonly attribute boolean rangeOverflow; readonly attribute boolean stepMismatch; -// readonly attribute boolean badInput; + readonly attribute boolean badInput; readonly attribute boolean customError; readonly attribute boolean valid; }; diff --git a/layout/forms/nsNumberControlFrame.cpp b/layout/forms/nsNumberControlFrame.cpp index 2eab578eafb6..c3637205f69e 100644 --- a/layout/forms/nsNumberControlFrame.cpp +++ b/layout/forms/nsNumberControlFrame.cpp @@ -595,6 +595,17 @@ nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue) #endif } +bool +nsNumberControlFrame::AnonTextControlIsEmpty() +{ + if (!mTextField) { + return true; + } + nsAutoString value; + HTMLInputElement::FromContent(mTextField)->GetValue(value); + return value.IsEmpty(); +} + Element* nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType) { diff --git a/layout/forms/nsNumberControlFrame.h b/layout/forms/nsNumberControlFrame.h index 31c9a3202494..cc5f9d1c259d 100644 --- a/layout/forms/nsNumberControlFrame.h +++ b/layout/forms/nsNumberControlFrame.h @@ -95,6 +95,8 @@ public: */ void GetValueOfAnonTextControl(nsAString& aValue); + bool AnonTextControlIsEmpty(); + /** * Called to notify this frame that its HTMLInputElement is currently * processing a DOM 'input' event. diff --git a/webapprt/locales/en-US/webapprt/overrides/dom.properties b/webapprt/locales/en-US/webapprt/overrides/dom.properties index e81b6ec0c197..3fa9b212d157 100644 --- a/webapprt/locales/en-US/webapprt/overrides/dom.properties +++ b/webapprt/locales/en-US/webapprt/overrides/dom.properties @@ -41,6 +41,7 @@ FormValidationRangeUnderflow=Please select a value that is higher than %S. FormValidationStepMismatch=Please select a valid value. The two nearest valid values are %S and %S. # LOCALIZATION NOTE (FormValidationStepMismatchOneValue): %S can be a number, a date or a time. This is called instead of FormValidationStepMismatch when the second value is the same as the first. FormValidationStepMismatchOneValue=Please select a valid value. The nearest valid value is %S. +FormValidationBadInputNumber=Please enter a number. GetAttributeNodeWarning=Use of getAttributeNode() is deprecated. Use getAttribute() instead. SetAttributeNodeWarning=Use of setAttributeNode() is deprecated. Use setAttribute() instead. GetAttributeNodeNSWarning=Use of getAttributeNodeNS() is deprecated. Use getAttributeNS() instead.