Bug 561636 (2/4) - Create new invalid form submission event and block invalid form submissions when there is an observer. r=smaug a2.0=blocking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Sat, 11 Sep 2010 06:07:41 +0200
changeset 52445 c8685734392d330014eab550db9af25bc8d93cc6
parent 52444 92ea14236f4689fe1cbec8829acd97d4a4ace77e
child 52446 8e70f3fc0d491b228b39092196fab274acb6d8f6
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)
reviewerssmaug
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 (2/4) - Create new invalid form submission event and block invalid form submissions when there is an observer. r=smaug a2.0=blocking
content/html/content/public/nsIFormSubmitObserver.idl
content/html/content/src/nsHTMLButtonElement.cpp
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLFormElement.h
content/html/content/src/nsHTMLInputElement.cpp
security/manager/boot/src/nsSecureBrowserUIImpl.h
--- a/content/html/content/public/nsIFormSubmitObserver.idl
+++ b/content/html/content/public/nsIFormSubmitObserver.idl
@@ -38,21 +38,26 @@
  * ***** END LICENSE BLOCK ***** */
 
 
 #include "nsISupports.idl"
 
 interface nsIDOMHTMLFormElement;
 interface nsIDOMWindowInternal;
 interface nsIURI;
+interface nsIArray;
 
-[scriptable, uuid(0787d64a-44bf-4273-8438-61ff13ebec0c)]
+[scriptable, uuid(0b3cc84d-25d2-448e-ae71-746ee6e41c2d)]
 interface nsIFormSubmitObserver: nsISupports
 {
   void notify(in nsIDOMHTMLFormElement formNode, in nsIDOMWindowInternal window, in nsIURI actionURL, out boolean cancelSubmit);
+
+  void notifyInvalidSubmit(in nsIDOMHTMLFormElement formNode,
+                           in nsIArray invalidElements);
 };
 
 %{C++
 #define NS_FORMSUBMIT_SUBJECT "formsubmit"
 #define NS_EARLYFORMSUBMIT_SUBJECT "earlyformsubmit"
 #define NS_FIRST_FORMSUBMIT_CATEGORY "firstformsubmit"
 #define NS_PASSWORDMANAGER_CATEGORY "passwordmanager"
+#define NS_INVALIDFORMSUBMIT_SUBJECT "invalidformsubmit"
 %}
--- a/content/html/content/src/nsHTMLButtonElement.cpp
+++ b/content/html/content/src/nsHTMLButtonElement.cpp
@@ -501,17 +501,20 @@ nsHTMLButtonElement::PostHandleEvent(nsE
         nsCOMPtr<nsIPresShell> presShell =
           aVisitor.mPresContext->GetPresShell();
         // If |nsIPresShell::Destroy| has been called due to
         // handling the event, the pres context will return
         // a null pres shell.  See bug 125624.
         //
         // Using presShell to dispatch the event. It makes sure that
         // event is not handled if the window is being destroyed.
-        if (presShell) {
+        if (presShell && (event.message != NS_FORM_SUBMIT ||
+                          mForm->CheckValidFormSubmission())) {
+          // TODO: removing this code and have the submit event sent by the form
+          // see bug 592124.
           // Hold a strong ref while dispatching
           nsRefPtr<nsHTMLFormElement> form(mForm);
           presShell->HandleDOMEventWithTarget(mForm, &event, &status);
         }
       }
     }
   } else if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
     // Tell the form to flush a possible pending submission.
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -47,16 +47,17 @@
 #include "nsDOMError.h"
 #include "nsContentUtils.h"
 #include "nsInterfaceHashtable.h"
 #include "nsContentList.h"
 #include "nsGUIEvent.h"
 #include "nsCOMArray.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
+#include "nsIMutableArray.h"
 
 // form submission
 #include "nsIFormSubmitObserver.h"
 #include "nsIObserverService.h"
 #include "nsICategoryManager.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsISimpleEnumerator.h"
 #include "nsRange.h"
@@ -407,17 +408,17 @@ nsHTMLFormElement::Reset()
   nsEventDispatcher::Dispatch(static_cast<nsIContent*>(this), nsnull,
                               &event);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLFormElement::CheckValidity(PRBool* retVal)
 {
-  *retVal = CheckFormValidity();
+  *retVal = CheckFormValidity(nsnull);
   return NS_OK;
 }
 
 PRBool
 nsHTMLFormElement::ParseAttribute(PRInt32 aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult)
@@ -695,26 +696,16 @@ nsHTMLFormElement::DoSubmit(nsEvent* aEv
   NS_ASSERTION(GetCurrentDoc(), "Should never get here without a current doc");
 
   if (mIsSubmitting) {
     NS_WARNING("Preventing double form submission");
     // XXX Should this return an error?
     return NS_OK;
   }
 
-#ifdef DEBUG
-  if (!CheckFormValidity()) {
-    printf("= The form is not valid!\n");
-#if 0
-    // TODO: uncomment this code whith a patch introducing a UI.
-    return NS_OK;
-#endif // 0
-  }
-#endif // DEBUG
-
   // Mark us as submitting so that we don't try to submit again
   mIsSubmitting = PR_TRUE;
   NS_ASSERTION(!mWebProgress && !mSubmittingRequest, "Web progress / submitting request should not exist here!");
 
   nsAutoPtr<nsFormSubmission> submission;
 
   //
   // prepare the submission object
@@ -1599,17 +1590,17 @@ nsHTMLFormElement::ForgetCurrentSubmissi
   nsCOMPtr<nsIWebProgress> webProgress = do_QueryReferent(mWebProgress);
   if (webProgress) {
     webProgress->RemoveProgressListener(this);
   }
   mWebProgress = nsnull;
 }
 
 PRBool
-nsHTMLFormElement::CheckFormValidity() const
+nsHTMLFormElement::CheckFormValidity(nsIMutableArray* aInvalidElements) const
 {
   PRBool ret = PR_TRUE;
 
   nsTArray<nsGenericHTMLFormElement*> sortedControls;
   if (NS_FAILED(mControls->GetSortedControls(sortedControls))) {
     return PR_FALSE;
   }
 
@@ -1626,31 +1617,107 @@ nsHTMLFormElement::CheckFormValidity() c
       continue;
     }
 
     nsCOMPtr<nsIConstraintValidation> cvElmt =
       do_QueryInterface((nsGenericHTMLElement*)sortedControls[i]);
     if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
         !cvElmt->IsValid()) {
       ret = PR_FALSE;
+      PRBool defaultAction = PR_TRUE;
       nsContentUtils::DispatchTrustedEvent(sortedControls[i]->GetOwnerDoc(),
                                            static_cast<nsIContent*>(sortedControls[i]),
                                            NS_LITERAL_STRING("invalid"),
-                                           PR_FALSE, PR_TRUE);
+                                           PR_FALSE, PR_TRUE, &defaultAction);
+
+      // Add all unhandled invalid controls to aInvalidElements if the caller
+      // requested them.
+      if (defaultAction && aInvalidElements) {
+        aInvalidElements->AppendElement((nsGenericHTMLElement*)sortedControls[i],
+                                        PR_FALSE);
+      }
     }
   }
 
   // Release the references.
   for (PRUint32 i = 0; i < len; ++i) {
     static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
   }
 
   return ret;
 }
 
+bool
+nsHTMLFormElement::CheckValidFormSubmission()
+{
+  /**
+   * Check for form validity: do not submit a form if there are unhandled
+   * invalid controls in the form.
+   * This should not be done if the form has been submitted with .submit().
+   *
+   * NOTE: for the moment, we are also checking that there is an observer for
+   * NS_INVALIDFORMSUBMIT_SUBJECT so it will prevent blocking form submission
+   * if the browser does not have implemented a UI yet.
+   *
+   * TODO: the check for observer should be removed later when HTML5 Forms will
+   * be spread enough and authors will assume forms can't be submitted when
+   * invalid. See bug 587671.
+   */
+
+  // When .submit() is called aEvent = nsnull so we can rely on that to know if
+  // we have to check the validity of the form.
+  nsCOMPtr<nsIObserverService> service =
+    mozilla::services::GetObserverService();
+  if (!service) {
+    NS_WARNING("No observer service available!");
+    return true;
+  }
+
+  nsCOMPtr<nsISimpleEnumerator> theEnum;
+  nsresult rv = service->EnumerateObservers(NS_INVALIDFORMSUBMIT_SUBJECT,
+                                            getter_AddRefs(theEnum));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool hasObserver = PR_FALSE;
+  rv = theEnum->HasMoreElements(&hasObserver);
+
+  // Do not check form validity if there is no observer for
+  // NS_INVALIDFORMSUBMIT_SUBJECT.
+  if (NS_SUCCEEDED(rv) && hasObserver) {
+    nsCOMPtr<nsIMutableArray> invalidElements =
+      do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!CheckFormValidity(invalidElements.get())) {
+      nsCOMPtr<nsISupports> inst;
+      nsCOMPtr<nsIFormSubmitObserver> observer;
+      PRBool more = PR_TRUE;
+      while (NS_SUCCEEDED(theEnum->HasMoreElements(&more)) && more) {
+        theEnum->GetNext(getter_AddRefs(inst));
+        observer = do_QueryInterface(inst);
+
+        if (observer) {
+          rv = observer->
+            NotifyInvalidSubmit(this,
+                                static_cast<nsIArray*>(invalidElements));
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+      }
+
+      // The form is invalid. Observers have been alerted. Do not submit.
+      return false;
+    }
+  } else {
+    NS_WARNING("There is no observer for \"invalidformsubmit\". \
+One should be implemented!");
+  }
+
+  return true;
+}
+
 void
 nsHTMLFormElement::UpdateValidity(PRBool aElementValidity)
 {
   if (aElementValidity) {
     --mInvalidElementsCount;
   } else {
     ++mInvalidElementsCount;
   }
--- a/content/html/content/src/nsHTMLFormElement.h
+++ b/content/html/content/src/nsHTMLFormElement.h
@@ -50,16 +50,17 @@
 #include "nsIURI.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsUnicharUtils.h"
 #include "nsThreadUtils.h"
 #include "nsInterfaceHashtable.h"
 
 class nsFormControlList;
+class nsIMutableArray;
 
 /**
  * hashkey wrapper using nsAString KeyType
  *
  * @see nsTHashtable::EntryType for specification
  */
 class nsStringCaseInsensitiveHashKey : public PLDHashEntryHdr
 {
@@ -266,16 +267,26 @@ public:
    * Returns the form validity based on the last UpdateValidity() call.
    *
    * @return Whether the form was valid the last time UpdateValidity() was called.
    *
    * @note This method may not return the *current* validity state!
    */
   PRBool GetValidity() const { return !mInvalidElementsCount; }
 
+  /**
+   * This method check the form validity and make invalid form elements send
+   * invalid event if needed.
+   *
+   * @return Whether the form is valid.
+   *
+   * @note This method might disappear with bug 592124, hopefuly.
+   */
+  bool CheckValidFormSubmission();
+
   virtual nsXPCClassInfo* GetClassInfo();
 protected:
   class RemoveElementRunnable;
   friend class RemoveElementRunnable;
 
   class RemoveElementRunnable : public nsRunnable {
   public:
     RemoveElementRunnable(nsHTMLFormElement* aForm, PRBool aNotify):
@@ -356,22 +367,22 @@ protected:
    * @param aOriginatingElement the originating element of the form submission [IN]
    */
   nsresult GetActionURL(nsIURI** aActionURL, nsIContent* aOriginatingElement);
 
   /**
    * Check the form validity following this algorithm:
    * http://www.whatwg.org/specs/web-apps/current-work/#statically-validate-the-constraints
    *
-   * TODO: add a [out] parameter to have the list of unhandled invalid controls
-   *       but not needed until we have a UI to test it.
+   * @param aInvalidElements [out] parameter containing the list of unhandled
+   * invalid controls.
    *
    * @return Whether the form is currently valid.
    */
-  PRBool CheckFormValidity() const;
+  PRBool CheckFormValidity(nsIMutableArray* aInvalidElements) const;
 
 public:
   /**
    * Flush a possible pending submission. If there was a scripted submission
    * triggered by a button or image, the submission was defered. This method
    * forces the pending submission to be submitted. (happens when the handler
    * returns false or there is an action/target change in the script)
    */
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -1635,17 +1635,19 @@ nsHTMLInputElement::MaybeSubmitForm(nsPr
   if (submitControl) {
     nsCOMPtr<nsIContent> submitContent(do_QueryInterface(submitControl));
     NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
     // Fire the button's onclick handler and let the button handle
     // submitting the form.
     nsMouseEvent event(PR_TRUE, NS_MOUSE_CLICK, nsnull, nsMouseEvent::eReal);
     nsEventStatus status = nsEventStatus_eIgnore;
     shell->HandleDOMEventWithTarget(submitContent, &event, &status);
-  } else if (mForm->HasSingleTextControl()) {
+  } else if (mForm->HasSingleTextControl() && mForm->CheckValidFormSubmission()) {
+    // TODO: removing this code and have the submit event sent by the form,
+    // bug 592124.
     // If there's only one text control, just submit the form
     // Hold strong ref across the event
     nsRefPtr<nsHTMLFormElement> form(mForm);
     nsFormEvent event(PR_TRUE, NS_FORM_SUBMIT);
     nsEventStatus status  = nsEventStatus_eIgnore;
     shell->HandleDOMEventWithTarget(mForm, &event, &status);
   }
 
@@ -2423,17 +2425,20 @@ nsHTMLInputElement::PostHandleEvent(nsEv
             nsEventStatus status  = nsEventStatus_eIgnore;
 
             nsCOMPtr<nsIPresShell> presShell =
               aVisitor.mPresContext->GetPresShell();
 
             // If |nsIPresShell::Destroy| has been called due to
             // handling the event the pres context will return a null
             // pres shell.  See bug 125624.
-            if (presShell) {
+            // TODO: removing this code and have the submit event sent by the
+            // form, see bug 592124.
+            if (presShell && (event.message != NS_FORM_SUBMIT ||
+                              mForm->CheckValidFormSubmission())) {
               // Hold a strong ref while dispatching
               nsRefPtr<nsHTMLFormElement> form(mForm);
               presShell->HandleDOMEventWithTarget(mForm, &event, &status);
             }
           }
           break;
 
         default:
--- a/security/manager/boot/src/nsSecureBrowserUIImpl.h
+++ b/security/manager/boot/src/nsSecureBrowserUIImpl.h
@@ -88,16 +88,18 @@ public:
   NS_DECL_NSISECUREBROWSERUI
   
   // nsIObserver
   NS_DECL_NSIOBSERVER
   NS_DECL_NSISSLSTATUSPROVIDER
 
   NS_IMETHOD Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindowInternal* window,
                     nsIURI *actionURL, PRBool* cancelSubmit);
+  NS_IMETHOD NotifyInvalidSubmit(nsIDOMHTMLFormElement* formNode,
+                                 nsIArray* invalidElements) { return NS_OK; };
   
 protected:
   PRMonitor *mMonitor;
   
   nsWeakPtr mWindow;
   nsCOMPtr<nsINetUtil> mIOService;
   nsCOMPtr<nsIStringBundle> mStringBundle;
   nsCOMPtr<nsIURI> mCurrentURI;