Bug 606817 - Truncate form validation message only if they are content specified. r=sicking,pike a=blocking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Tue, 26 Oct 2010 00:02:24 +0200
changeset 56464 5796d98f408496e7439e7d41158bb2b0f6cf64e1
parent 56463 5696f9ba01ad925187b795c3d72bf082c34c368e
child 56466 c7c309313eb39fc37d75e02111fcaac6edfc66c2
push idunknown
push userunknown
push dateunknown
reviewerssicking, pike, blocking
bugs606817
milestone2.0b8pre
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 606817 - Truncate form validation message only if they are content specified. r=sicking,pike a=blocking
browser/base/content/browser.js
browser/base/content/test/browser_bug561636.js
content/html/content/public/nsIConstraintValidation.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsIConstraintValidation.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug606817.html
dom/locales/en-US/chrome/dom/dom.properties
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -814,18 +814,17 @@ const gFormSubmitObserver = {
 
     if (!(element instanceof HTMLInputElement ||
           element instanceof HTMLTextAreaElement ||
           element instanceof HTMLSelectElement ||
           element instanceof HTMLButtonElement)) {
       return;
     }
 
-    // Limit the message to 256 characters.
-    this.panel.firstChild.textContent = element.validationMessage.substring(0, 256);
+    this.panel.firstChild.textContent = element.validationMessage;
 
     element.focus();
 
     // If the user type something or blur the element, we want to remove the popup.
     // We could check for clicks but a click is already removing the popup.
     let eventHandler = function(e) {
       gFormSubmitObserver.panel.hidePopup();
     };
--- a/browser/base/content/test/browser_bug561636.js
+++ b/browser/base/content/test/browser_bug561636.js
@@ -12,18 +12,18 @@ function checkPopupHide()
 {
   ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open',
      "The invalid form popup should not be shown");
 }
 
 function checkPopupMessage(doc)
 {
   is(gInvalidFormPopup.firstChild.textContent,
-     doc.getElementById('i').validationMessage.substring(0,256),
-     "The panel should show the 256 first characters of the validationMessage");
+     doc.getElementById('i').validationMessage,
+     "The panel should show the message from validationMessage");
 }
 
 let gObserver = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
 
   notifyInvalidSubmit : function (aFormElement, aInvalidElements)
   {
   }
@@ -128,74 +128,75 @@ function test3()
     gBrowser.contentDocument.getElementById('s').click();
   }, true);
 
   gBrowser.selectedTab = tab;
   gBrowser.selectedTab.linkedBrowser.loadURI(uri);
 }
 
 /**
- * In this test, we check that the validation message is correctly cut.
+ * In this test, we check that, we can hide the popup by interacting with the
+ * invalid element.
  */
 function test4()
 {
-  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i'><input id='s' type='submit'></form>";
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
   let tab = gBrowser.addTab();
 
   gInvalidFormPopup.addEventListener("popupshown", function() {
     gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
 
     let doc = gBrowser.contentDocument;
     is(doc.activeElement, doc.getElementById('i'),
        "First invalid element should be focused");
 
     checkPopupShow();
     checkPopupMessage(doc);
 
-    // Clean-up and next test.
-    gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
-    executeSoon(test5);
+    EventUtils.synthesizeKey("a", {});
+
+    executeSoon(function () {
+      checkPopupHide();
+
+      // Clean-up and next test.
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+      executeSoon(test5);
+    });
   }, false);
 
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
 
-    let msg = "";
-    for (let i=0; i<50; ++i) {
-      msg += "abcde ";
-    }
-    // msg has 300 characters
-    gBrowser.contentDocument.getElementById('i').setCustomValidity(msg);
     gBrowser.contentDocument.getElementById('s').click();
   }, true);
 
   gBrowser.selectedTab = tab;
   gBrowser.selectedTab.linkedBrowser.loadURI(uri);
 }
 
 /**
- * In this test, we check that, we can hide the popup by interacting with the
- * invalid element.
+ * In this test, we check that we can hide the popup by blurring the invalid
+ * element.
  */
 function test5()
 {
   let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
   let tab = gBrowser.addTab();
 
   gInvalidFormPopup.addEventListener("popupshown", function() {
     gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
 
     let doc = gBrowser.contentDocument;
     is(doc.activeElement, doc.getElementById('i'),
        "First invalid element should be focused");
 
     checkPopupShow();
     checkPopupMessage(doc);
 
-    EventUtils.synthesizeKey("a", {});
+    doc.getElementById('i').blur();
 
     executeSoon(function () {
       checkPopupHide();
 
       // Clean-up and next test.
       gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
       executeSoon(test6);
     });
@@ -207,35 +208,34 @@ function test5()
     gBrowser.contentDocument.getElementById('s').click();
   }, true);
 
   gBrowser.selectedTab = tab;
   gBrowser.selectedTab.linkedBrowser.loadURI(uri);
 }
 
 /**
- * In this test, we check that we can hide the popup by blurring the invalid
- * element.
+ * In this test, we check that we can hide the popup by pressing TAB.
  */
 function test6()
 {
   let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
   let tab = gBrowser.addTab();
 
   gInvalidFormPopup.addEventListener("popupshown", function() {
     gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
 
     let doc = gBrowser.contentDocument;
     is(doc.activeElement, doc.getElementById('i'),
        "First invalid element should be focused");
 
     checkPopupShow();
     checkPopupMessage(doc);
 
-    doc.getElementById('i').blur();
+    EventUtils.synthesizeKey("VK_TAB", {});
 
     executeSoon(function () {
       checkPopupHide();
 
       // Clean-up and next test.
       gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
       executeSoon(test7);
     });
@@ -247,82 +247,43 @@ function test6()
     gBrowser.contentDocument.getElementById('s').click();
   }, true);
 
   gBrowser.selectedTab = tab;
   gBrowser.selectedTab.linkedBrowser.loadURI(uri);
 }
 
 /**
- * In this test, we check that we can hide the popup by pressing TAB.
+ * In this test, we check that the popup will hide if we move to another tab.
  */
 function test7()
 {
   let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
   let tab = gBrowser.addTab();
 
   gInvalidFormPopup.addEventListener("popupshown", function() {
     gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
 
     let doc = gBrowser.contentDocument;
     is(doc.activeElement, doc.getElementById('i'),
        "First invalid element should be focused");
 
     checkPopupShow();
     checkPopupMessage(doc);
 
-    EventUtils.synthesizeKey("VK_TAB", {});
-
-    executeSoon(function () {
-      checkPopupHide();
-
-      // Clean-up and next test.
-      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
-      executeSoon(test8);
-    });
-  }, false);
-
-  tab.linkedBrowser.addEventListener("load", function(aEvent) {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-
-    gBrowser.contentDocument.getElementById('s').click();
-  }, true);
-
-  gBrowser.selectedTab = tab;
-  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
-}
-
-/**
- * In this test, we check that the popup will hide if we move to another tab.
- */
-function test8()
-{
-  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
-  let tab = gBrowser.addTab();
-
-  gInvalidFormPopup.addEventListener("popupshown", function() {
-    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
-
-    let doc = gBrowser.contentDocument;
-    is(doc.activeElement, doc.getElementById('i'),
-       "First invalid element should be focused");
-
-    checkPopupShow();
-    checkPopupMessage(doc);
-
     // Create a new tab and move to it.
     gBrowser.selectedTab  = gBrowser.addTab("about:blank", {skipAnimation: true});
 
     executeSoon(function() {
       checkPopupHide();
 
       // Clean-up and next test.
       gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
       gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
-      executeSoon(test9);
+      executeSoon(test8);
     });
   }, false);
 
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
 
     gBrowser.contentDocument.getElementById('s').click();
   }, true);
@@ -331,17 +292,17 @@ function test8()
   gBrowser.selectedTab.linkedBrowser.loadURI(uri);
 }
 
 /**
  * In this test, we check that nothing happen (no focus nor popup) if the
  * invalid form is submitted in another tab than the current focused one
  * (submitted in background).
  */
-function test9()
+function test8()
 {
   let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
   let tab = gBrowser.addTab();
 
   gObserver.notifyInvalidSubmit = function() {
     executeSoon(function() {
       let doc = tab.linkedBrowser.contentDocument;
       isnot(doc.activeElement, doc.getElementById('i'),
@@ -350,17 +311,17 @@ function test9()
       checkPopupHide();
 
       // Clean-up
       Services.obs.removeObserver(gObserver, "invalidformsubmit");
       gObserver.notifyInvalidSubmit = function () {};
       gBrowser.removeTab(tab, {animate: false});
 
       // Next test
-      executeSoon(test10);
+      executeSoon(test9);
     });
   };
 
   Services.obs.addObserver(gObserver, "invalidformsubmit", false);
 
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
 
@@ -371,17 +332,17 @@ function test9()
   }, true);
 
   tab.linkedBrowser.loadURI(uri);
 }
 
 /**
  * In this test, we check that the author defined error message is shown.
  */
-function test10()
+function test9()
 {
   let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input x-moz-errormessage='foo' required id='i'><input id='s' type='submit'></form>";
   let tab = gBrowser.addTab();
 
   gInvalidFormPopup.addEventListener("popupshown", function() {
     gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
 
     let doc = gBrowser.contentDocument;
--- a/content/html/content/public/nsIConstraintValidation.h
+++ b/content/html/content/public/nsIConstraintValidation.h
@@ -59,16 +59,18 @@ class nsIDOMValidityState;
 class nsIConstraintValidation : public nsISupports
 {
 public:
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONSTRAINTVALIDATION_IID);
 
   friend class nsDOMValidityState;
 
+  static const PRUint16 sContentSpecifiedMaxLengthMessage;
+
   virtual ~nsIConstraintValidation();
 
   PRBool IsValid() const { return mValidityBitField == 0; }
 
   PRBool IsCandidateForConstraintValidation() const {
            return !mBarredFromConstraintValidation;
          }
 
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -3967,16 +3967,19 @@ nsHTMLInputElement::GetValidationMessage
       nsXPIDLString message;
       nsAutoString title;
       GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
       if (title.IsEmpty()) {
         rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                 "FormValidationPatternMismatch",
                                                 message);
       } else {
+        if (title.Length() > nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
+          title.Truncate(nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
+        }
         const PRUnichar* params[] = { title.get() };
         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                    "FormValidationPatternMismatchWithTitle",
                                                    params, 1, message);
       }
       aValidationMessage = message;
       break;
     }
--- a/content/html/content/src/nsIConstraintValidation.cpp
+++ b/content/html/content/src/nsIConstraintValidation.cpp
@@ -40,16 +40,18 @@
 #include "nsAString.h"
 #include "nsGenericHTMLElement.h"
 #include "nsHTMLFormElement.h"
 #include "nsDOMValidityState.h"
 #include "nsIFormControl.h"
 #include "nsHTMLFormElement.h"
 
 
+const PRUint16 nsIConstraintValidation::sContentSpecifiedMaxLengthMessage = 256;
+
 nsIConstraintValidation::nsIConstraintValidation()
   : mValidityBitField(0)
   , mValidity(nsnull)
   // By default, all elements are subjects to constraint validation.
   , mBarredFromConstraintValidation(PR_FALSE)
 {
 }
 
@@ -82,18 +84,24 @@ nsIConstraintValidation::GetValidationMe
     NS_ASSERTION(content, "This class should be inherited by HTML elements only!");
 
     nsAutoString authorMessage;
     content->GetAttr(kNameSpaceID_None, nsGkAtoms::x_moz_errormessage,
                      authorMessage);
 
     if (!authorMessage.IsEmpty()) {
       aValidationMessage.Assign(authorMessage);
+      if (aValidationMessage.Length() > sContentSpecifiedMaxLengthMessage) {
+        aValidationMessage.Truncate(sContentSpecifiedMaxLengthMessage);
+      }
     } else if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR)) {
       aValidationMessage.Assign(mCustomValidity);
+      if (aValidationMessage.Length() > sContentSpecifiedMaxLengthMessage) {
+        aValidationMessage.Truncate(sContentSpecifiedMaxLengthMessage);
+      }
     } else if (GetValidityState(VALIDITY_STATE_TOO_LONG)) {
       GetValidationMessage(aValidationMessage, VALIDITY_STATE_TOO_LONG);
     } else if (GetValidityState(VALIDITY_STATE_VALUE_MISSING)) {
       GetValidationMessage(aValidationMessage, VALIDITY_STATE_VALUE_MISSING);
     } else if (GetValidityState(VALIDITY_STATE_TYPE_MISMATCH)) {
       GetValidationMessage(aValidationMessage, VALIDITY_STATE_TYPE_MISMATCH);
     } else if (GetValidityState(VALIDITY_STATE_PATTERN_MISMATCH)) {
       GetValidationMessage(aValidationMessage, VALIDITY_STATE_PATTERN_MISMATCH);
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -230,12 +230,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug557087-4.html \
 		test_bug557087-5.html \
 		test_bug557087-6.html \
 		test_bug586763.html \
 		test_bug598643.html \
 		test_bug596350.html \
 		test_bug600155.html \
 		test_bug556007.html \
+		test_bug606817.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug606817.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=606817
+-->
+<head>
+  <title>Test for Bug 606817</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=606817">Mozilla Bug 606817</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 606817 **/
+
+var messageMaxLength = 256;
+
+function checkMessage(aInput, aMsg, aWithTerminalPeriod)
+{
+  ok(aInput.validationMessage != aMsg,
+     "Original content-defined message should have been truncate");
+  is(aInput.validationMessage.length - aInput.validationMessage.indexOf("_42_"),
+     aWithTerminalPeriod ? messageMaxLength+1 : messageMaxLength,
+     "validation message should be 256 characters length");
+}
+
+var input = document.createElement("input");
+
+var msg = "";
+for (var i=0; i<75; ++i) {
+  msg += "_42_";
+}
+// msg is now 300 chars long
+
+// Testing with setCustomValidity().
+input.setCustomValidity(msg);
+checkMessage(input, msg, false);
+
+// The input is still invalid but x-moz-errormessage will be used as the message.
+input.setAttribute("x-moz-errormessage", msg);
+checkMessage(input, msg, false);
+
+// Cleaning.
+input.setCustomValidity("");
+input.removeAttribute("x-moz-errormessage");
+
+// Testing with pattern and titl.
+input.pattern = "[0-9]*";
+input.value = "foo";
+input.title = msg;
+checkMessage(input, msg, true);
+
+// Cleaning.
+input.removeAttribute("pattern");
+input.removeAttribute("title");
+input.value = "";
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -69,11 +69,12 @@ FormValidationTextTooLong=Please shorten
 FormValidationValueMissing=Please fill out this field.
 FormValidationCheckboxMissing=Please check this box if you want to proceed.
 FormValidationRadioMissing=Please select one of these options.
 FormValidationFileMissing=Please select a file.
 FormValidationSelectMissing=Please select an item in the list.
 FormValidationInvalidEmail=Please enter an email address.
 FormValidationInvalidURL=Please enter a URL.
 FormValidationPatternMismatch=Please match the requested format.
+# LOCALIZATION NOTE (FormValidationPatternMismatchWithTitle): %S is the (possibly truncated) title attribute value.
 FormValidationPatternMismatchWithTitle=Please match the requested format: %S.
 UseOfDocumentWidthWarning=Non-standard document.width was used. Use standard document.body.clientWidth instead.
 UseOfDocumentHeightWarning=Non-standard document.height was used. Use standard document.body.clientHeight instead.