Bug 581947 - Show validation message error in the tooltip of an invalid element. r=gavin.sharp sr=sicking ui-r=limi a2.0=blocking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 27 Aug 2010 07:46:17 +0200
changeset 51550 6a95227f6b078e43f8f461612d1b0c2b63b6399a
parent 51549 bb9d266da12c3b2926e196178ffbfaa2e1d8d7f6
child 51551 d052c765cc04ecac3978d4f0c50e48c0d7689728
push idunknown
push userunknown
push dateunknown
reviewersgavin, sicking, limi
bugs581947
milestone2.0b5pre
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 581947 - Show validation message error in the tooltip of an invalid element. r=gavin.sharp sr=sicking ui-r=limi a2.0=blocking
browser/base/content/browser.js
browser/base/content/test/Makefile.in
browser/base/content/test/browser_bug581947.js
content/html/content/public/Makefile.in
content/html/content/public/nsIConstraintValidation.h
content/html/content/src/nsIConstraintValidation.cpp
content/html/content/src/nsIConstraintValidation.h
embedding/browser/webBrowser/nsDocShellTreeOwner.cpp
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2784,16 +2784,27 @@ function FillInHTMLTooltip(tipElement)
   var SVGTitleText = null;
 #ifdef MOZ_SVG
   var lookingForSVGTitle = true;
 #else
   var lookingForSVGTitle = false;
 #endif // MOZ_SVG
   var direction = tipElement.ownerDocument.dir;
 
+  // If the element is invalid per HTML5 Forms specifications,
+  // show the constraint validation error message instead of @tooltip.
+  if (tipElement instanceof HTMLInputElement ||
+      tipElement instanceof HTMLTextAreaElement ||
+      tipElement instanceof HTMLSelectElement ||
+      tipElement instanceof HTMLButtonElement) {
+    // If the element is barred from constraint validation or valid,
+    // the validation message will be the empty string.
+    titleText = tipElement.validationMessage;
+  }
+
   while (!titleText && !XLinkTitleText && !SVGTitleText && tipElement) {
     if (tipElement.nodeType == Node.ELEMENT_NODE) {
       titleText = tipElement.getAttribute("title");
       if ((tipElement instanceof HTMLAnchorElement && tipElement.href) ||
           (tipElement instanceof HTMLAreaElement && tipElement.href) ||
           (tipElement instanceof HTMLLinkElement && tipElement.href)
 #ifdef MOZ_SVG
           || (tipElement instanceof SVGAElement && tipElement.hasAttributeNS(XLinkNS, "href"))
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -137,16 +137,17 @@ endif
                  browser_bug555767.js \
                  browser_bug556061.js \
                  browser_bug561623.js \
                  browser_bug562649.js \
                  browser_bug563588.js \
                  browser_bug577121.js \
                  browser_bug580956.js \
                  browser_bug581242.js \
+                 browser_bug581947.js \
                  browser_bug585830.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
                  browser_discovery.js \
                  browser_drag.js \
                  browser_duplicateIDs.js \
                  browser_gestureSupport.js \
                  browser_getshortcutoruri.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug581947.js
@@ -0,0 +1,85 @@
+function check(aElementName, aBarred, aType) {
+  let doc = gBrowser.contentDocument;
+  let tooltip = document.getElementById("aHTMLTooltip");
+  let content = doc.getElementById('content');
+
+  let e = doc.createElement(aElementName);
+  content.appendChild(e);
+
+  if (aType) {
+    e.type = aType;
+  }
+
+  ok(!FillInHTMLTooltip(e),
+     "No tooltip should be shown when the element is valid");
+
+  e.setCustomValidity('foo');
+  if (aBarred) {
+    ok(!FillInHTMLTooltip(e),
+       "No tooltip should be shown when the element is barred from constraint validation");
+  } else {
+    ok(FillInHTMLTooltip(e),
+       "A tooltip should be shown when the element isn't valid");
+  }
+
+  content.removeChild(e);
+}
+
+function todo_check(aElementName, aBarred) {
+  let doc = gBrowser.contentDocument;
+  let tooltip = document.getElementById("aHTMLTooltip");
+  let content = doc.getElementById('content');
+
+  let e = doc.createElement(aElementName);
+  content.appendChild(e);
+
+  let cought = false;
+  try {
+    e.setCustomValidity('foo');
+  } catch (e) {
+    cought = true;
+  }
+
+  todo(!cought, "setCustomValidity should exist for " + aElementName);
+
+  content.removeChild(e);
+}
+
+function test () {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function () {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    let testData = [
+    /* element name, barred, type */
+      [ 'input',    false, null],
+      [ 'textarea', false, null],
+      [ 'button',   true,  'button'],
+      [ 'button',   false, 'submit' ],
+      [ 'select',   false, null],
+      [ 'output',   true,  null],
+      [ 'fieldset', true,  null],
+    ];
+
+    for each (let data in testData) {
+      check(data[0], data[1], data[2]);
+    }
+
+    let todo_testData = [
+      [ 'keygen', 'false' ],
+      [ 'object', 'false' ],
+    ];
+
+    for each(let data in todo_testData) {
+      todo_check(data[0], data[1]);
+    }
+
+    gBrowser.removeCurrentTab();
+    finish();
+  }, true);
+
+  content.location = 
+    "data:text/html,<!DOCTYPE html><html><body><div id='content'></div></body></html>";
+}
+
--- a/content/html/content/public/Makefile.in
+++ b/content/html/content/public/Makefile.in
@@ -47,16 +47,17 @@ XPIDL_MODULE = content_html
 
 XPIDLSRCS = \
 		nsISelectElement.idl \
 		nsIFormSubmitObserver.idl \
 		nsIPhonetic.idl \
 		$(NULL)
 
 EXPORTS = \
+		nsIConstraintValidation.h \
 		nsIFormControl.h \
 		nsIForm.h \
 		nsIFormProcessor.h \
 		nsILink.h \
 		nsIRadioVisitor.h \
 		nsIRadioGroupContainer.h \
 		nsITextControlElement.h \
 		nsIFileControlElement.h \
rename from content/html/content/src/nsIConstraintValidation.h
rename to content/html/content/public/nsIConstraintValidation.h
--- a/content/html/content/src/nsIConstraintValidation.h
+++ b/content/html/content/public/nsIConstraintValidation.h
@@ -66,16 +66,18 @@ public:
   friend class nsDOMValidityState;
 
   virtual ~nsIConstraintValidation();
 
   PRBool IsValid() const { return mValidityBitField == 0; }
 
   PRBool IsCandidateForConstraintValidation() const;
 
+  NS_IMETHOD GetValidationMessage(nsAString& aValidationMessage);
+
 protected:
 
   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
@@ -84,17 +86,16 @@ protected:
     VALIDITY_STATE_STEP_MISMATCH    = 0x40, // 0b01000000
     VALIDITY_STATE_CUSTOM_ERROR     = 0x80  // 0b10000000
   };
 
   // You can't instantiate an object from that class.
   nsIConstraintValidation();
 
   nsresult GetValidity(nsIDOMValidityState** aValidity);
-  nsresult GetValidationMessage(nsAString& aValidationMessage);
   nsresult CheckValidity(PRBool* aValidity);
   void     SetCustomValidity(const nsAString& aError);
 
   bool GetValidityState(ValidityStateType mState) const {
          return mValidityBitField & mState;
        }
 
   void   SetValidityState(ValidityStateType mState, PRBool mValue) {
--- a/content/html/content/src/nsIConstraintValidation.cpp
+++ b/content/html/content/src/nsIConstraintValidation.cpp
@@ -63,17 +63,17 @@ nsIConstraintValidation::GetValidity(nsI
     mValidity = new nsDOMValidityState(this);
   }
 
   NS_ADDREF(*aValidity = mValidity);
 
   return NS_OK;
 }
 
-nsresult
+NS_IMETHODIMP
 nsIConstraintValidation::GetValidationMessage(nsAString& aValidationMessage)
 {
   aValidationMessage.Truncate();
 
   if (IsCandidateForConstraintValidation() && !IsValid()) {
     if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR)) {
       aValidationMessage.Assign(mCustomValidity);
     } else if (GetValidityState(VALIDITY_STATE_TOO_LONG)) {
--- a/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp
+++ b/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp
@@ -104,16 +104,17 @@
 #include "nsContextMenuInfo.h"
 #include "nsPresContext.h"
 #include "nsIViewManager.h"
 #include "nsIView.h"
 #include "nsPIDOMEventTarget.h"
 #include "nsIEventListenerManager.h"
 #include "nsIDOMEventGroup.h"
 #include "nsIDOMDragEvent.h"
+#include "nsIConstraintValidation.h"
 
 //
 // GetEventReceiver
 //
 // A helper routine that navigates the tricky path from a |nsWebBrowser| to
 // a |nsPIDOMEventTarget| via the window root and chrome event handler.
 //
 static nsresult
@@ -1098,16 +1099,25 @@ DefaultTooltipTextProvider::GetNodeText(
   NS_ENSURE_ARG_POINTER(aNode);
   NS_ENSURE_ARG_POINTER(aText);
     
   nsString outText;
 
   PRBool lookingForSVGTitle = PR_TRUE;
   PRBool found = PR_FALSE;
   nsCOMPtr<nsIDOMNode> current ( aNode );
+
+  // If the element implement the constraint validation API,
+  // show the validation message, if any, instead of the title.
+  nsCOMPtr<nsIConstraintValidation> cvElement = do_QueryInterface(current);
+  if (cvElement) {
+    cvElement->GetValidationMessage(outText);
+    found = !outText.IsEmpty();
+  }
+
   while ( !found && current ) {
     nsCOMPtr<nsIDOMElement> currElement ( do_QueryInterface(current) );
     if ( currElement ) {
       nsCOMPtr<nsIContent> content(do_QueryInterface(currElement));
       if (content) {
         nsIAtom *tagAtom = content->Tag();
         if (tagAtom != mTag_dialog &&
             tagAtom != mTag_dialogheader &&