Bug 561636 (4/4) - When an invalid form is submitted, an error messages should be displayed. r=dolske a2.0=blocking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Sat, 11 Sep 2010 06:10:31 +0200
changeset 52447 e3f194205a3bd556d471bb69c2621a9457945c76
parent 52446 8e70f3fc0d491b228b39092196fab274acb6d8f6
child 52448 8235bdf7c65eb9d84da729439f00df39fc05671c
push id15645
push usermlamouri@mozilla.com
push dateSat, 11 Sep 2010 04:22:15 +0000
treeherdermozilla-central@2779c55431a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske
bugs561636
milestone2.0b6pre
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 561636 (4/4) - When an invalid form is submitted, an error messages should be displayed. r=dolske a2.0=blocking
browser/base/content/browser.css
browser/base/content/browser.js
browser/base/content/browser.xul
browser/themes/gnomestripe/browser/browser.css
browser/themes/pinstripe/browser/browser.css
browser/themes/winstripe/browser/browser.css
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -367,11 +367,15 @@ window[chromehidden~="toolbar"] toolbar:
 
 #notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon,
 #notification-popup-box[anchorid="indexedDB-notification-icon"] > #indexedDB-notification-icon,
 #notification-popup-box[anchorid="addons-notification-icon"] > #addons-notification-icon,
 #notification-popup-box[anchorid="password-notification-icon"] > #password-notification-icon {
   display: -moz-box;
 }
 
+#invalid-form-popup {
+  max-width: 280px;
+}
+
 #geolocation-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#geolocation-notification");
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -784,16 +784,82 @@ const gXPInstallObserver = {
 
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
       break;
     }
   }
 };
 
+const gFormSubmitObserver = {
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+  panel: null,
+
+  init: function()
+  {
+    this.panel = document.getElementById('invalid-form-popup');
+    this.panel.appendChild(document.createTextNode(""));
+  },
+
+  panelIsOpen: function()
+  {
+    return this.panel && this.panel.state != "hiding" &&
+           this.panel.state != "closed";
+  },
+
+  notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+  {
+    // We are going to handle invalid form submission attempt by focusing the
+    // first invalid element and show the corresponding validation message in a
+    // panel attached to the element.
+    if (!aInvalidElements.length) {
+      return;
+    }
+
+    // Don't show the popup if the current tab doesn't contain the invalid form.
+    if (gBrowser.selectedTab.linkedBrowser.contentDocument !=
+        aFormElement.ownerDocument) {
+      return;
+    }
+
+    let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+
+    if (!(element instanceof HTMLInputElement ||
+          element instanceof HTMLTextAreaElement ||
+          element instanceof HTMLSelectElement ||
+          element instanceof HTMLButtonElement)) {
+      return;
+    }
+
+    // Limit the message to 256 characters.
+    this.panel.firstChild.nodeValue = element.validationMessage.substring(0, 256);
+
+    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();
+    };
+    element.addEventListener("input", eventHandler, false);
+    element.addEventListener("blur", eventHandler, false);
+
+    // One event to bring them all and in the darkness bind them all.
+    this.panel.addEventListener("popuphiding", function(aEvent) {
+      aEvent.target.removeEventListener("popuphiding", arguments.callee, false);
+      element.removeEventListener("input", eventHandler, false);
+      element.removeEventListener("blur", eventHandler, false);
+    }, false);
+
+    this.panel.hidden = false;
+    this.panel.openPopup(element, "after_start", 0, 0);
+  }
+};
+
 // Simple gestures support
 //
 // As per bug #412486, web content must not be allowed to receive any
 // simple gesture events.  Multi-touch gesture APIs are in their
 // infancy and we do NOT want to be forced into supporting an API that
 // will probably have to change in the future.  (The current Mac OS X
 // API is undocumented and was reverse-engineered.)  Until support is
 // implemented in the event dispatcher to keep these events as
@@ -1313,20 +1379,22 @@ function prepareForStartup() {
   gGestureSupport.init(true);
 }
 
 function delayedStartup(isLoadingBlank, mustLoadSidebar) {
   Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+  Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
   BrowserOffline.init();
   OfflineApps.init();
   IndexedDBPromptHelper.init();
+  gFormSubmitObserver.init();
 
   gBrowser.addEventListener("pageshow", function(evt) { setTimeout(pageShowEventHandlers, 0, evt); }, true);
 
   // Ensure login manager is up and running.
   Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
 
   if (mustLoadSidebar) {
     let sidebar = document.getElementById("sidebar");
@@ -1557,16 +1625,17 @@ function BrowserShutdown()
     Components.utils.reportError(ex);
   }
 
   Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
   Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
+  Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
 
   try {
     gBrowser.removeProgressListener(window.XULBrowserWindow);
     gBrowser.removeTabsProgressListener(window.TabsProgressListener);
   } catch (ex) {
   }
 
   PlacesStarButton.uninit();
@@ -4186,16 +4255,21 @@ var XULBrowserWindow = {
       }
     }
   },
 
   onLocationChange: function (aWebProgress, aRequest, aLocationURI) {
     var location = aLocationURI ? aLocationURI.spec : "";
     this._hostChanged = true;
 
+    // Hide the form invalid popup.
+    if (gFormSubmitObserver.panelIsOpen()) {
+      gFormSubmitObserver.panel.hidePopup();
+    }
+
     if (document.tooltipNode) {
       // Optimise for the common case
       if (aWebProgress.DOMWindow == content) {
         document.getElementById("aHTMLTooltip").hidePopup();
         document.tooltipNode = null;
       }
       else {
         for (let tooltipWindow =
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -158,16 +158,19 @@
     <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
 
     <!-- for search and content formfill/pw manager -->
     <panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
 
     <!-- for url bar autocomplete -->
     <panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
 
+    <!-- for invalid form error message -->
+    <panel id="invalid-form-popup" noautofocus="true" hidden="true" level="parent"/>
+
     <panel id="editBookmarkPanel"
            orient="vertical"
            ignorekeys="true"
            hidden="true"
            onpopupshown="StarUI.panelShown(event);"
            aria-labelledby="editBookmarkPanelTitle">
       <row id="editBookmarkPanelHeader" align="center" hidden="true">
         <vbox align="center">
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -1003,16 +1003,25 @@ toolbar[iconsize="small"] #fullscreen-bu
 }
 
 /* Identity popup bounding box */
 #identity-popup-container {
   min-width: 280px;
   padding: 10px;
 }
 
+/* Invalid form popup */
+#invalid-form-popup {
+  -moz-appearance: none;
+  background-color: #fffcd6;
+  border: 1px solid #dad8b6;
+  padding: 5px 5px 5px 5px;
+  font-weight: bold;
+}
+
 /* Notification popup */
 #notification-popup {
   margin: 4px 0;
   min-width: 280px;
   padding: 10px;
 }
 
 .popup-notification-icon {
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -1905,16 +1905,25 @@ toolbarbutton.chevron > .toolbarbutton-m
   -moz-window-shadow: none;
   background-color: transparent;
   margin-top: -3px;
   margin-left: -23px;
   min-width: 280px;
   -moz-border-image: url(chrome://browser/skin/hud-panel.png) 26 18 22 50 / 26px 18px 22px 50px repeat;
 }
 
+/* Invalid form popup */
+#invalid-form-popup {
+  -moz-appearance: none;
+  background-color: #fffcd6;
+  border: 1px solid #dad8b6;
+  padding: 5px 5px 5px 5px;
+  font-weight: bold;
+}
+
 #notification-popup {
   color: #fff;
   margin-top: -1px;
   margin-left: -27px;
 }
 
 #notification-popup-box {
   margin: 0 3px;
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -1852,16 +1852,25 @@ toolbarbutton.bookmark-item[dragover="tr
 
 /* Popup Bounding Box */
 #identity-popup,
 #notification-popup {
   -moz-appearance: menupopup;
   color: MenuText;
 }
 
+/* Invalid form popup */
+#invalid-form-popup {
+  -moz-appearance: none;
+  background-color: #fffcd6;
+  border: 1px solid #dad8b6;
+  padding: 5px 5px 5px 5px;
+  font-weight: bold;
+}
+
 /* Notification popup */
 #notification-popup {
   padding: 10px;
   margin-top: 3px;
 }
 
 .popup-notification-icon {
   width: 64px;