Bug 605124 (4/5) - :-moz-ui-invalid applies if the user tried to submit the form in an invalid state. r=bz a=bsmedberg
authorMounir Lamouri <mounir.lamouri@gmail.com>
Wed, 24 Nov 2010 00:50:19 +0100
changeset 58146 5be85a60b133680a48ead8085bb9f7cca9c2e016
parent 58145 15b5a12e17ac6e2dd3cf4829aa381b44be160ea2
child 58147 253651b1e9c2f5baf3e2bb423994f51ec502a2e6
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersbz, bsmedberg
bugs605124
milestone2.0b8pre
Bug 605124 (4/5) - :-moz-ui-invalid applies if the user tried to submit the form in an invalid state. r=bz a=bsmedberg
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLFormElement.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLTextAreaElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug605124.html
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -260,17 +260,18 @@ nsHTMLFormElement::nsHTMLFormElement(alr
     mNotifiedObserversResult(PR_FALSE),
     mSubmitPopupState(openAbused),
     mSubmitInitiatedFromUserInput(PR_FALSE),
     mPendingSubmission(nsnull),
     mSubmittingRequest(nsnull),
     mDefaultSubmitElement(nsnull),
     mFirstSubmitInElements(nsnull),
     mFirstSubmitNotInElements(nsnull),
-    mInvalidElementsCount(0)
+    mInvalidElementsCount(0),
+    mEverTriedInvalidSubmit(false)
 {
 }
 
 nsHTMLFormElement::~nsHTMLFormElement()
 {
   if (mControls) {
     mControls->DropFormReference();
   }
@@ -472,16 +473,20 @@ MarkOrphans(const nsTArray<nsGenericHTML
 
 static void
 CollectOrphans(nsINode* aRemovalRoot, nsTArray<nsGenericHTMLFormElement*> aArray
 #ifdef DEBUG
                , nsIDOMHTMLFormElement* aThisForm
 #endif
                )
 {
+  // Prepare document update batch.
+  nsIDocument* doc = aArray.IsEmpty() ? nsnull : aArray[0]->GetCurrentDoc();
+  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
+
   // Walk backwards so that if we remove elements we can just keep iterating
   PRUint32 length = aArray.Length();
   for (PRUint32 i = length; i > 0; --i) {
     nsGenericHTMLFormElement* node = aArray[i-1];
 
     // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
     // node is in fact a descendant of the form and hence should stay in the
     // form.  If it _is_ set, then we need to check whether the node is a
@@ -490,24 +495,28 @@ CollectOrphans(nsINode* aRemovalRoot, ns
 #ifdef DEBUG
     PRBool removed = PR_FALSE;
 #endif
     if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
       node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
       if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
         node->ClearForm(PR_TRUE);
 
-        // When submit controls have no more form, they need to be updated.
+        // When a form control loses its form owner,
+        // NS_EVENT_STATE_MOZ_UI_INVALID might not apply any more.
+        nsEventStates states = NS_EVENT_STATE_MOZ_UI_INVALID;
+
+        // In addition, submit controls shouldn't have
+        // NS_EVENT_STATE_MOZ_SUBMITINVALID applying if they do not have a form.
         if (node->IsSubmitControl()) {
-          nsIDocument* doc = node->GetCurrentDoc();
-          if (doc) {
-            MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
-            doc->ContentStatesChanged(node, nsnull,
-                                      NS_EVENT_STATE_MOZ_SUBMITINVALID);
-          }
+          states |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
+        }
+
+        if (doc) {
+          doc->ContentStatesChanged(node, nsnull, states);
         }
 #ifdef DEBUG
         removed = PR_TRUE;
 #endif
       }
     }
 
 #ifdef DEBUG
@@ -1700,20 +1709,49 @@ nsHTMLFormElement::CheckValidFormSubmiss
       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);
+          observer->NotifyInvalidSubmit(this,
+                                        static_cast<nsIArray*>(invalidElements));
+        }
+      }
+
+      // For the first invalid submission, we should update element states.
+      if (!mEverTriedInvalidSubmit) {
+        mEverTriedInvalidSubmit = true;
+
+        nsIDocument* doc = GetCurrentDoc();
+        if (doc) {
+          /*
+           * We are going to call ContentStatesChanged assuming elements want to
+           * be notified because we can't know.
+           * Submissions shouldn't happen during parsing so it _should_ be safe.
+           */
+
+          MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
+
+          for (PRUint32 i = 0, length = mControls->mElements.Length();
+               i < length; ++i) {
+            doc->ContentStatesChanged(mControls->mElements[i], nsnull,
+                                      NS_EVENT_STATE_MOZ_UI_INVALID);
+          }
+
+          // Because of backward compatibility, <input type='image'> is not in
+          // elements but can be invalid.
+          // TODO: should probably be removed when bug 606491 will be fixed.
+          for (PRUint32 i = 0, length = mControls->mNotInElements.Length();
+               i < length; ++i) {
+            doc->ContentStatesChanged(mControls->mNotInElements[i], nsnull,
+                                      NS_EVENT_STATE_MOZ_UI_INVALID);
+          }
         }
       }
 
       // The form is invalid. Observers have been alerted. Do not submit.
       return false;
     }
   } else {
     NS_WARNING("There is no observer for \"invalidformsubmit\". \
--- a/content/html/content/src/nsHTMLFormElement.h
+++ b/content/html/content/src/nsHTMLFormElement.h
@@ -291,16 +291,25 @@ public:
   /**
    * Walk over the form elements and call SubmitNamesValues() on them to get
    * their data pumped into the FormSubmitter.
    *
    * @param aFormSubmission the form submission object
    */
   nsresult WalkFormElements(nsFormSubmission* aFormSubmission);
 
+  /**
+   * Whether the submission of this form has been ever prevented because of
+   * being invalid.
+   *
+   * @return Whether the submission of this form has been prevented because of
+   * being invalid.
+   */
+  bool HasEverTriedInvalidSubmit() const { return mEverTriedInvalidSubmit; }
+
 protected:
   class RemoveElementRunnable;
   friend class RemoveElementRunnable;
 
   class RemoveElementRunnable : public nsRunnable {
   public:
     RemoveElementRunnable(nsHTMLFormElement* aForm)
       : mForm(aForm)
@@ -436,16 +445,22 @@ protected:
 
   /**
    * Number of invalid and candidate for constraint validation elements in the
    * form the last time UpdateValidity has been called.
    * @note Should only be used by UpdateValidity() and GetValidity()!
    */
   PRInt32 mInvalidElementsCount;
 
+  /**
+   * Whether the submission of this form has been ever prevented because of
+   * being invalid.
+   */
+  bool mEverTriedInvalidSubmit;
+
 protected:
   /** Detection of first form to notify observers */
   static PRBool gFirstFormSubmitted;
   /** Detection of first password input to initialize the password manager */
   static PRBool gPasswordManagerInitialized;
 };
 
 #endif // nsHTMLFormElement_h__
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -3311,21 +3311,24 @@ nsHTMLInputElement::IntrinsicState() con
     } else {
       state |= NS_EVENT_STATE_INVALID;
       ValueModeType valueMode = GetValueMode();
       // If the element is suffering from VALIDITY_STATE_CUSTOM_ERROR,
       // NS_EVENT_STATE_MOZ_UI_INVALID always apply.
       // Otherwise, NS_EVENT_STATE_MOZ_UI_INVALID applies if the element's value
       // has been modified.
       // For VALUE_MODE_DEFAULT case, value being modified has no sense.
-      if (valueMode == VALUE_MODE_DEFAULT ||
-          GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
-          (valueMode == VALUE_MODE_DEFAULT_ON && GetCheckedChanged()) ||
-          ((valueMode == VALUE_MODE_FILENAME || valueMode == VALUE_MODE_VALUE) &&
-           GET_BOOLBIT(mBitField, BF_VALUE_CHANGED))) {
+      // NS_EVENT_STATE_MOZ_UI_INVALID always applies if the form submission has
+      // been tried while invalid.
+      if ((mForm && mForm->HasEverTriedInvalidSubmit()) ||
+          (valueMode == VALUE_MODE_DEFAULT ||
+           GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+           (valueMode == VALUE_MODE_DEFAULT_ON && GetCheckedChanged()) ||
+           ((valueMode == VALUE_MODE_FILENAME || valueMode == VALUE_MODE_VALUE) &&
+            GET_BOOLBIT(mBitField, BF_VALUE_CHANGED)))) {
         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
       }
     }
   }
 
   if (PlaceholderApplies() && HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
       !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
     // TODO: we really need a GetValue(...) const method, see bug 585097
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -73,16 +73,17 @@
 #include "nsLayoutUtils.h"
 #include "nsLayoutErrors.h"
 #include "nsStubMutationObserver.h"
 #include "nsDOMError.h"
 #include "mozAutoDocUpdate.h"
 #include "nsISupportsPrimitives.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsIConstraintValidation.h"
+#include "nsHTMLFormElement.h"
 
 #include "nsTextEditorState.h"
 
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kXULControllersCID,  NS_XULCONTROLLERS_CID);
 
 #define NS_NO_CONTENT_DISPATCH (1 << 0)
@@ -1014,17 +1015,20 @@ nsHTMLTextAreaElement::IntrinsicState() 
   if (IsCandidateForConstraintValidation()) {
     if (IsValid()) {
       state |= NS_EVENT_STATE_VALID;
     } else {
       state |= NS_EVENT_STATE_INVALID;
       // NS_EVENT_STATE_MOZ_UI_INVALID always apply if the element suffers from
       // VALIDITY_STATE_CUSTOM_ERROR.
       // Otherwise, it applies if the value has been modified.
-      if (mValueChanged || GetValidityState(VALIDITY_STATE_CUSTOM_ERROR)) {
+      // NS_EVENT_STATE_MOZ_UI_INVALID always applies if the form submission has
+      // been tried while invalid.
+      if ((mForm && mForm->HasEverTriedInvalidSubmit()) ||
+          (mValueChanged || GetValidityState(VALIDITY_STATE_CUSTOM_ERROR))) {
         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
       }
     }
   }
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
       !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
     nsAutoString value;
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -238,12 +238,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug606817.html \
 		test_bug297761.html \
 		file_bug297761.html \
 		test_bug607145.html \
 		test_bug601061.html \
 		test_bug596511.html \
 		reflect.js \
 		test_bug613113.html \
+		test_bug605124.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_bug605124.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=605124
+-->
+<head>
+  <title>Test for Bug 605124</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=605124">Mozilla Bug 605124</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <form>
+    <textarea required></textarea>
+    <input required>
+    <button type='submit'></button>
+  </form>
+
+  <table>
+    <form>
+    <tr>
+      <textarea required></textarea>
+      <input required>
+      <button type='submit'></button>
+    </tr>
+    </form>
+  </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 605124 **/
+
+function checkPseudoClass(aElement, aExpected)
+{
+  is(aElement.mozMatchesSelector(":-moz-ui-invalid"), aExpected,
+     "mozMatchesSelector(':-moz-ui-invalid') should return " + aExpected + " for " + aElement);
+}
+
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+var os = Components.classes['@mozilla.org/observer-service;1']
+                   .getService(Components.interfaces.nsIObserverService);
+var observers = os.enumerateObservers("invalidformsubmit");
+
+if (observers.hasMoreElements()) {
+  var content = document.getElementById('content');
+  var textarea = document.getElementsByTagName('textarea')[0];
+  var input = document.getElementsByTagName('input')[0];
+  var button = document.getElementsByTagName('button')[0];
+  var form = document.forms[0];
+
+  checkPseudoClass(textarea, false);
+  checkPseudoClass(input, false);
+
+  // Try to submit.
+  button.click();
+  checkPseudoClass(textarea, true);
+  checkPseudoClass(input, true);
+
+  // No longer in the form.
+  content.appendChild(textarea);
+  content.appendChild(input);
+  checkPseudoClass(textarea, false);
+  checkPseudoClass(input, false);
+
+  // Back in the form.
+  form.appendChild(textarea);
+  form.appendChild(input);
+  checkPseudoClass(textarea, true);
+  checkPseudoClass(input, true);
+
+  /* Case when elements get orphaned. */
+  var textarea = document.getElementsByTagName('textarea')[1];
+  var input = document.getElementsByTagName('input')[1];
+  var button = document.getElementsByTagName('button')[1];
+  var form = document.forms[1];
+
+  // Try to submit.
+  button.click();
+  checkPseudoClass(textarea, true);
+  checkPseudoClass(input, true);
+
+  // Remove the form.
+  document.getElementsByTagName('table')[0].removeChild(form);
+  checkPseudoClass(textarea, false);
+  checkPseudoClass(input, false);
+}
+
+</script>
+</pre>
+</body>
+</html>