Bug 580575 - Implement :-moz-submit-invalid pseudo-class applying on submit buttons when the form is invalid. r=bz sr=sicking a2.0=roc
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 10 Sep 2010 07:08:56 +0200
changeset 52361 e5fd1e37beb404f004251821b528da3e53c5dae3
parent 52360 5380df03991bd6c027ade28479f2dc24897bc46e
child 52362 43ae28d1240d9adebe0d33f4544c728ee5343432
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, sicking
bugs580575
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 580575 - Implement :-moz-submit-invalid pseudo-class applying on submit buttons when the form is invalid. r=bz sr=sicking a2.0=roc
content/events/public/nsIEventStateManager.h
content/html/content/public/nsIConstraintValidation.h
content/html/content/src/nsHTMLButtonElement.cpp
content/html/content/src/nsHTMLFieldSetElement.cpp
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLFormElement.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/src/nsHTMLObjectElement.cpp
content/html/content/src/nsHTMLOutputElement.cpp
content/html/content/src/nsHTMLSelectElement.cpp
content/html/content/src/nsHTMLSelectElement.h
content/html/content/src/nsHTMLTextAreaElement.cpp
content/html/content/src/nsIConstraintValidation.cpp
layout/reftests/css-submit-invalid/button-submit/add-invalid-element.html
layout/reftests/css-submit-invalid/button-submit/add-submit-control.html
layout/reftests/css-submit-invalid/button-submit/change-type-not-submit-control.html
layout/reftests/css-submit-invalid/button-submit/change-type-submit-control.html
layout/reftests/css-submit-invalid/button-submit/dynamic-invalid-barred-2.html
layout/reftests/css-submit-invalid/button-submit/dynamic-invalid-barred.html
layout/reftests/css-submit-invalid/button-submit/dynamic-invalid-not-barred.html
layout/reftests/css-submit-invalid/button-submit/dynamic-invalid.html
layout/reftests/css-submit-invalid/button-submit/dynamic-valid.html
layout/reftests/css-submit-invalid/button-submit/invalid-barred-ref.html
layout/reftests/css-submit-invalid/button-submit/invalid-ref-2.html
layout/reftests/css-submit-invalid/button-submit/invalid-ref-3.html
layout/reftests/css-submit-invalid/button-submit/invalid-ref.html
layout/reftests/css-submit-invalid/button-submit/reftest.list
layout/reftests/css-submit-invalid/button-submit/remove-form.html
layout/reftests/css-submit-invalid/button-submit/remove-invalid-element.html
layout/reftests/css-submit-invalid/button-submit/remove-submit-control.html
layout/reftests/css-submit-invalid/button-submit/self-invalid.html
layout/reftests/css-submit-invalid/button-submit/static-invalid-barred.html
layout/reftests/css-submit-invalid/button-submit/static-invalid.html
layout/reftests/css-submit-invalid/button-submit/static-valid.html
layout/reftests/css-submit-invalid/button-submit/valid-ref-2.html
layout/reftests/css-submit-invalid/button-submit/valid-ref-3.html
layout/reftests/css-submit-invalid/button-submit/valid-ref-4.html
layout/reftests/css-submit-invalid/button-submit/valid-ref.html
layout/reftests/css-submit-invalid/input-image/add-invalid-element.html
layout/reftests/css-submit-invalid/input-image/add-submit-control.html
layout/reftests/css-submit-invalid/input-image/change-type-not-submit-control.html
layout/reftests/css-submit-invalid/input-image/change-type-submit-control.html
layout/reftests/css-submit-invalid/input-image/dynamic-invalid-barred-2.html
layout/reftests/css-submit-invalid/input-image/dynamic-invalid-barred.html
layout/reftests/css-submit-invalid/input-image/dynamic-invalid-not-barred.html
layout/reftests/css-submit-invalid/input-image/dynamic-invalid.html
layout/reftests/css-submit-invalid/input-image/dynamic-valid.html
layout/reftests/css-submit-invalid/input-image/invalid-barred-ref.html
layout/reftests/css-submit-invalid/input-image/invalid-ref-2.html
layout/reftests/css-submit-invalid/input-image/invalid-ref-3.html
layout/reftests/css-submit-invalid/input-image/invalid-ref.html
layout/reftests/css-submit-invalid/input-image/reftest.list
layout/reftests/css-submit-invalid/input-image/remove-form.html
layout/reftests/css-submit-invalid/input-image/remove-invalid-element.html
layout/reftests/css-submit-invalid/input-image/remove-submit-control.html
layout/reftests/css-submit-invalid/input-image/self-invalid.html
layout/reftests/css-submit-invalid/input-image/static-invalid-barred.html
layout/reftests/css-submit-invalid/input-image/static-invalid.html
layout/reftests/css-submit-invalid/input-image/static-valid.html
layout/reftests/css-submit-invalid/input-image/valid-ref-2.html
layout/reftests/css-submit-invalid/input-image/valid-ref-3.html
layout/reftests/css-submit-invalid/input-image/valid-ref-4.html
layout/reftests/css-submit-invalid/input-image/valid-ref.html
layout/reftests/css-submit-invalid/input-submit/add-invalid-element.html
layout/reftests/css-submit-invalid/input-submit/add-submit-control.html
layout/reftests/css-submit-invalid/input-submit/change-type-not-submit-control.html
layout/reftests/css-submit-invalid/input-submit/change-type-submit-control.html
layout/reftests/css-submit-invalid/input-submit/dynamic-invalid-barred-2.html
layout/reftests/css-submit-invalid/input-submit/dynamic-invalid-barred.html
layout/reftests/css-submit-invalid/input-submit/dynamic-invalid-not-barred.html
layout/reftests/css-submit-invalid/input-submit/dynamic-invalid.html
layout/reftests/css-submit-invalid/input-submit/dynamic-valid.html
layout/reftests/css-submit-invalid/input-submit/invalid-barred-ref.html
layout/reftests/css-submit-invalid/input-submit/invalid-ref-2.html
layout/reftests/css-submit-invalid/input-submit/invalid-ref-3.html
layout/reftests/css-submit-invalid/input-submit/invalid-ref.html
layout/reftests/css-submit-invalid/input-submit/reftest.list
layout/reftests/css-submit-invalid/input-submit/remove-form.html
layout/reftests/css-submit-invalid/input-submit/remove-invalid-element.html
layout/reftests/css-submit-invalid/input-submit/remove-submit-control.html
layout/reftests/css-submit-invalid/input-submit/self-invalid.html
layout/reftests/css-submit-invalid/input-submit/static-invalid-barred.html
layout/reftests/css-submit-invalid/input-submit/static-invalid.html
layout/reftests/css-submit-invalid/input-submit/static-valid.html
layout/reftests/css-submit-invalid/input-submit/valid-ref-2.html
layout/reftests/css-submit-invalid/input-submit/valid-ref-3.html
layout/reftests/css-submit-invalid/input-submit/valid-ref-4.html
layout/reftests/css-submit-invalid/input-submit/valid-ref.html
layout/reftests/css-submit-invalid/reftest.list
layout/reftests/reftest.list
layout/style/nsCSSPseudoClassList.h
--- a/content/events/public/nsIEventStateManager.h
+++ b/content/events/public/nsIEventStateManager.h
@@ -215,9 +215,21 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIEventSt
                                      (1 << 28)
 
 // content has focus and should show a ring
 #define NS_EVENT_STATE_FOCUSRING     (1 << 29)
 
 // Content shows its placeholder
 #define NS_EVENT_STATE_MOZ_PLACEHOLDER (1 << 30)
 
+// Content is a submit control and the form isn't valid.
+#define NS_EVENT_STATE_MOZ_SUBMITINVALID (1U << 31)
+
+/**
+ * WARNING:
+ * (1U << 31) should work but we currently handle event states with PRInt32
+ * so it's an edge case.
+ * DO NOT ADD AN EVENT STATE after NS_EVENT_STATE_MOZ_SUBMITINVALID until we
+ * move to PRUint64 and we introduce a type to handle event states.
+ * See bug 595036.
+ */
+
 #endif // nsIEventStateManager_h__
--- a/content/html/content/public/nsIConstraintValidation.h
+++ b/content/html/content/public/nsIConstraintValidation.h
@@ -39,17 +39,16 @@
 #define nsIConstraintValidition_h___
 
 #include "nsISupports.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
 class nsDOMValidityState;
 class nsIDOMValidityState;
-class nsGenericHTMLFormElement;
 
 #define NS_ICONSTRAINTVALIDATION_IID \
 { 0xca3824dc, 0x4f5c, 0x4878, \
  { 0xa6, 0x8a, 0x95, 0x54, 0x5f, 0xfa, 0x4b, 0xf9 } }
 
 /**
  * This interface is for form elements implementing the validity constraint API.
  * See: http://dev.w3.org/html5/spec/forms.html#the-constraint-validation-api
@@ -64,17 +63,19 @@ public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONSTRAINTVALIDATION_IID);
 
   friend class nsDOMValidityState;
 
   virtual ~nsIConstraintValidation();
 
   PRBool IsValid() const { return mValidityBitField == 0; }
 
-  PRBool IsCandidateForConstraintValidation() const;
+  PRBool IsCandidateForConstraintValidation() const {
+           return !mBarredFromConstraintValidation;
+         }
 
   NS_IMETHOD GetValidationMessage(nsAString& aValidationMessage);
 
 protected:
 
   enum ValidityStateType
   {
     VALIDITY_STATE_VALUE_MISSING    = 0x01, // 0b00000001
@@ -93,25 +94,20 @@ protected:
   nsresult GetValidity(nsIDOMValidityState** aValidity);
   nsresult CheckValidity(PRBool* aValidity);
   void     SetCustomValidity(const nsAString& aError);
 
   bool GetValidityState(ValidityStateType mState) const {
          return mValidityBitField & mState;
        }
 
-  void   SetValidityState(ValidityStateType mState, PRBool mValue) {
-           if (mValue) {
-             mValidityBitField |= mState;
-           } else {
-             mValidityBitField &= ~mState;
-           }
-         }
+  void SetValidityState(ValidityStateType mState,
+                        PRBool mValue);
 
-  virtual PRBool   IsBarredFromConstraintValidation() const { return PR_FALSE; }
+  void SetBarredFromConstraintValidation(PRBool aBarred);
 
   virtual nsresult GetValidationMessage(nsAString& aValidationMessage,
                                         ValidityStateType aType) {
                      return NS_OK;
                    }
 
 private:
 
@@ -122,16 +118,21 @@ private:
   PRInt8                        mValidityBitField;
 
   /**
    * A pointer to the ValidityState object.
    */
   nsRefPtr<nsDOMValidityState>  mValidity;
 
   /**
+   * Keeps track whether the element is barred from constraint validation.
+   */
+  PRBool                        mBarredFromConstraintValidation;
+
+  /**
    * The string representing the custom error.
    */
   nsString                      mCustomValidity;
 };
 
 /**
  * Use these macro for class inheriting from nsIConstraintValidation to forward
  * functions to nsIConstraintValidation.
--- a/content/html/content/src/nsHTMLButtonElement.cpp
+++ b/content/html/content/src/nsHTMLButtonElement.cpp
@@ -132,17 +132,17 @@ public:
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
   virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual void DoneCreatingElement();
   virtual nsXPCClassInfo* GetClassInfo();
 
   // nsIConstraintValidation
-  PRBool IsBarredFromConstraintValidation() const;
+  void UpdateBarredFromConstraintValidation();
 
 protected:
   virtual PRBool AcceptAutofocus() const
   {
     return PR_TRUE;
   }
 
   PRUint8 mType;
@@ -610,28 +610,37 @@ nsHTMLButtonElement::BeforeSetAttr(PRInt
   return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
                                                  aValue, aNotify);
 }
 
 nsresult
 nsHTMLButtonElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                   const nsAString* aValue, PRBool aNotify)
 {
-  if (aNameSpaceID == kNameSpaceID_None &&
-      aName == nsGkAtoms::type) {
-    if (!aValue) {
-      mType = kButtonDefaultType->value;
+  PRInt32 states = 0;
+
+  if (aNameSpaceID == kNameSpaceID_None) {
+    if (aName == nsGkAtoms::type) {
+      if (!aValue) {
+        mType = kButtonDefaultType->value;
+      }
+
+      UpdateBarredFromConstraintValidation();
+      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
+                NS_EVENT_STATE_MOZ_SUBMITINVALID;
+    } else if (aName == nsGkAtoms::disabled) {
+      UpdateBarredFromConstraintValidation();
+      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID;
     }
 
-    if (aNotify) {
+    if (aNotify && states) {
       nsIDocument* doc = GetCurrentDoc();
       if (doc) {
         MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
-        doc->ContentStatesChanged(this, nsnull,
-                                  NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID);
+        doc->ContentStatesChanged(this, nsnull, states);
       }
     }
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
                                                 aValue, aNotify);
 }
 
@@ -667,16 +676,20 @@ PRInt32
 nsHTMLButtonElement::IntrinsicState() const
 {
   PRInt32 state = nsGenericHTMLFormElement::IntrinsicState();
 
   if (IsCandidateForConstraintValidation()) {
     state |= IsValid() ? NS_EVENT_STATE_VALID : NS_EVENT_STATE_INVALID;
   }
 
+  if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
+    state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
+  }
+
   return state | NS_EVENT_STATE_OPTIONAL;
 }
 
 // nsIConstraintValidation
 
 NS_IMETHODIMP
 nsHTMLButtonElement::SetCustomValidity(const nsAString& aError)
 {
@@ -687,15 +700,17 @@ nsHTMLButtonElement::SetCustomValidity(c
     MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
     doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_INVALID |
                                             NS_EVENT_STATE_VALID);
   }
 
   return NS_OK;
 }
 
-PRBool
-nsHTMLButtonElement::IsBarredFromConstraintValidation() const
+void
+nsHTMLButtonElement::UpdateBarredFromConstraintValidation()
 {
-  return (mType == NS_FORM_BUTTON_BUTTON ||
-          mType == NS_FORM_BUTTON_RESET);
+  SetBarredFromConstraintValidation(mType == NS_FORM_BUTTON_BUTTON ||
+                                    mType == NS_FORM_BUTTON_RESET ||
+                                    HasAttr(kNameSpaceID_None,
+                                            nsGkAtoms::disabled));
 }
 
--- a/content/html/content/src/nsHTMLFieldSetElement.cpp
+++ b/content/html/content/src/nsHTMLFieldSetElement.cpp
@@ -70,30 +70,29 @@ public:
   NS_DECL_NSIDOMHTMLFIELDSETELEMENT
 
   // nsIFormControl
   NS_IMETHOD_(PRUint32) GetType() const { return NS_FORM_FIELDSET; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual nsXPCClassInfo* GetClassInfo();
-
-  // nsIConstraintValidation
-  PRBool IsBarredFromConstraintValidation() const { return PR_TRUE; };
 };
 
 // construction, destruction
 
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet)
 
 
 nsHTMLFieldSetElement::nsHTMLFieldSetElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLFormElement(aNodeInfo)
 {
+  // <fieldset> is always barred from constraint validation.
+  SetBarredFromConstraintValidation(PR_TRUE);
 }
 
 nsHTMLFieldSetElement::~nsHTMLFieldSetElement()
 {
 }
 
 // nsISupports
 
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -75,16 +75,17 @@
 #include "nsLayoutUtils.h"
 
 #include "nsEventDispatcher.h"
 
 #include "mozAutoDocUpdate.h"
 #include "nsIHTMLCollection.h"
 
 #include "nsIConstraintValidation.h"
+#include "nsIEventStateManager.h"
 
 static const int NS_FORM_CONTROL_LIST_HASHTABLE_SIZE = 16;
 
 // nsHTMLFormElement
 
 PRBool nsHTMLFormElement::gFirstFormSubmitted = PR_FALSE;
 PRBool nsHTMLFormElement::gPasswordManagerInitialized = PR_FALSE;
 
@@ -245,17 +246,18 @@ nsHTMLFormElement::nsHTMLFormElement(alr
     mNotifiedObservers(PR_FALSE),
     mNotifiedObserversResult(PR_FALSE),
     mSubmitPopupState(openAbused),
     mSubmitInitiatedFromUserInput(PR_FALSE),
     mPendingSubmission(nsnull),
     mSubmittingRequest(nsnull),
     mDefaultSubmitElement(nsnull),
     mFirstSubmitInElements(nsnull),
-    mFirstSubmitNotInElements(nsnull)
+    mFirstSubmitNotInElements(nsnull),
+    mInvalidElementsCount(0)
 {
 }
 
 nsHTMLFormElement::~nsHTMLFormElement()
 {
   if (mControls) {
     mControls->DropFormReference();
   }
@@ -479,16 +481,26 @@ CollectOrphans(nsINode* aRemovalRoot, ns
     // also the code in nsGenericHTMLFormElement::FindForm.
 #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, PR_TRUE);
+
+        // When submit controls have no more form, they need to be updated.
+        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);
+          }
+        }
 #ifdef DEBUG
         removed = PR_TRUE;
 #endif
       }
     }
 
 #ifdef DEBUG
     if (!removed) {
@@ -1195,16 +1207,25 @@ nsHTMLFormElement::AddElement(nsGenericH
         MOZ_AUTO_DOC_UPDATE(document, UPDATE_CONTENT_STATE, PR_TRUE);
         nsCOMPtr<nsIContent> oldElement(do_QueryInterface(oldDefaultSubmit));
         document->ContentStatesChanged(oldElement, nsnull,
                                        NS_EVENT_STATE_DEFAULT);
       }
     }
   }
 
+  // If the element is subject to constraint validaton and is invalid, we need
+  // to update our internal counter.
+  nsCOMPtr<nsIConstraintValidation> cvElmt =
+    do_QueryInterface(static_cast<nsGenericHTMLElement*>(aChild));
+  if (cvElmt &&
+      cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
+    UpdateValidity(PR_FALSE);
+  }
+
   return NS_OK;
 }
 
 nsresult
 nsHTMLFormElement::AddElementToTable(nsGenericHTMLFormElement* aChild,
                                      const nsAString& aName)
 {
   return mControls->AddElementToTable(aChild, aName);  
@@ -1262,16 +1283,25 @@ nsHTMLFormElement::RemoveElement(nsGener
     nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this, aNotify));
 
     // Note that we don't need to notify on the old default submit (which is
     // being removed) because it's either being removed from the DOM or
     // changing attributes in a way that makes it responsible for sending its
     // own notifications.
   }
 
+  // If the element was subject to constraint validaton and is invalid, we need
+  // to update our internal counter.
+  nsCOMPtr<nsIConstraintValidation> cvElmt =
+    do_QueryInterface(static_cast<nsGenericHTMLElement*>(aChild));
+  if (cvElmt &&
+      cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
+    UpdateValidity(PR_TRUE);
+  }
+
   return rv;
 }
 
 void
 nsHTMLFormElement::HandleDefaultSubmitRemoval(PRBool aNotify)
 {
   if (mDefaultSubmitElement) {
     // Already got reset somehow; nothing else to do here
@@ -1611,16 +1641,70 @@ nsHTMLFormElement::CheckFormValidity() c
   // Release the references.
   for (PRUint32 i = 0; i < len; ++i) {
     static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
   }
 
   return ret;
 }
 
+void
+nsHTMLFormElement::UpdateValidity(PRBool aElementValidity)
+{
+  if (aElementValidity) {
+    --mInvalidElementsCount;
+  } else {
+    ++mInvalidElementsCount;
+  }
+
+  NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!");
+
+  // The form validity has just changed if:
+  // - there are no more invalid elements ;
+  // - or there is one invalid elmement and an element just became invalid.
+  // If we have invalid elements and we used to before as well, do nothing.
+  if (mInvalidElementsCount &&
+      (mInvalidElementsCount != 1 || aElementValidity)) {
+    return;
+  }
+
+  nsIDocument* doc = GetCurrentDoc();
+  if (!doc) {
+    return;
+  }
+
+  /*
+   * We are going to call ContentStatesChanged assuming submit controls want to
+   * be notified because we can't know.
+   * UpdateValidity shouldn't be called so much during parsing so it _should_
+   * be safe.
+   */
+
+  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
+
+  // Inform submit controls that the form validity has changed.
+  for (PRUint32 i = 0, length = mControls->mElements.Length();
+       i < length; ++i) {
+    if (mControls->mElements[i]->IsSubmitControl()) {
+      doc->ContentStatesChanged(mControls->mElements[i], nsnull,
+                                NS_EVENT_STATE_MOZ_SUBMITINVALID);
+    }
+  }
+
+  // Because of backward compatibility, <input type='image'> is not in elements
+  // so we have to check for controls not in elements too.
+  PRUint32 length = mControls->mNotInElements.Length();
+  for (PRUint32 i = 0; i < length; ++i) {
+    if (mControls->mNotInElements[i]->IsSubmitControl()) {
+      doc->ContentStatesChanged(mControls->mNotInElements[i], nsnull,
+                                NS_EVENT_STATE_MOZ_SUBMITINVALID);
+    }
+  }
+}
+
 // nsIWebProgressListener
 NS_IMETHODIMP
 nsHTMLFormElement::OnStateChange(nsIWebProgress* aWebProgress,
                                  nsIRequest* aRequest,
                                  PRUint32 aStateFlags,
                                  PRUint32 aStatus)
 {
   // If STATE_STOP is never fired for any reason (redirect?  Failed state
--- a/content/html/content/src/nsHTMLFormElement.h
+++ b/content/html/content/src/nsHTMLFormElement.h
@@ -29,16 +29,20 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsHTMLFormElement_h__
+#define nsHTMLFormElement_h__
+
 #include "nsCOMPtr.h"
 #include "nsIForm.h"
 #include "nsIFormControl.h"
 #include "nsFormSubmission.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMHTMLFormElement.h"
 #include "nsIDOMNSHTMLFormElement.h"
 #include "nsIWebProgressListener.h"
@@ -239,16 +243,39 @@ public:
   /**
    * Flag the form to know that a button or image triggered scripted form
    * submission. In that case the form will defer the submission until the
    * script handler returns and the return value is known.
    */
   void OnSubmitClickBegin(nsIContent* aOriginatingElement);
   void OnSubmitClickEnd();
 
+  /**
+   * This method will update the form validity so the submit controls states
+   * will be updated (for -moz-submit-invalid pseudo-class).
+   * This method has to be called by form elements whenever their validity state
+   * or status regarding constraint validation changes.
+   *
+   * @note This method isn't used for CheckValidity().
+   * @note If an element becomes barred from constraint validation, it has to be
+   * considered as valid.
+   *
+   * @param aElementValidityState the new validity state of the element
+   */
+  void UpdateValidity(PRBool aElementValidityState);
+
+  /**
+   * 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; }
+
   virtual nsXPCClassInfo* GetClassInfo();
 protected:
   class RemoveElementRunnable;
   friend class RemoveElementRunnable;
 
   class RemoveElementRunnable : public nsRunnable {
   public:
     RemoveElementRunnable(nsHTMLFormElement* aForm, PRBool aNotify):
@@ -386,14 +413,23 @@ protected:
   nsGenericHTMLFormElement* mDefaultSubmitElement;
 
   /** The first submit element in mElements -- WEAK */
   nsGenericHTMLFormElement* mFirstSubmitInElements;
 
   /** The first submit element in mNotInElements -- WEAK */
   nsGenericHTMLFormElement* mFirstSubmitNotInElements;
 
+  /**
+   * 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;
+
 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
@@ -845,17 +845,19 @@ nsHTMLInputElement::AfterSetAttr(PRInt32
 
     if (aName == nsGkAtoms::type) {
       if (!aValue) {
         // We're now a text input.  Note that we have to handle this manually,
         // since removing an attribute (which is what happened, since aValue is
         // null) doesn't call ParseAttribute.
         HandleTypeChange(kInputDefaultType->value);
       }
-    
+
+      UpdateBarredFromConstraintValidation();
+
       // If we are changing type from File/Text/Tel/Passwd to other input types
       // we need save the mValue into value attribute
       if (mInputData.mValue &&
           mType != NS_FORM_INPUT_EMAIL &&
           mType != NS_FORM_INPUT_TEXT &&
           mType != NS_FORM_INPUT_SEARCH &&
           mType != NS_FORM_INPUT_PASSWORD &&
           mType != NS_FORM_INPUT_TEL &&
@@ -894,22 +896,29 @@ nsHTMLInputElement::AfterSetAttr(PRInt32
                 NS_EVENT_STATE_LOADING |
                 NS_EVENT_STATE_MOZ_READONLY |
                 NS_EVENT_STATE_MOZ_READWRITE |
                 NS_EVENT_STATE_REQUIRED |
                 NS_EVENT_STATE_OPTIONAL |
                 NS_EVENT_STATE_VALID |
                 NS_EVENT_STATE_INVALID |
                 NS_EVENT_STATE_INDETERMINATE |
-                NS_EVENT_STATE_MOZ_PLACEHOLDER;
+                NS_EVENT_STATE_MOZ_PLACEHOLDER |
+                NS_EVENT_STATE_MOZ_SUBMITINVALID;
     }
 
     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
         aName == nsGkAtoms::readonly) {
       UpdateValueMissingValidityState();
+
+      // This *has* to be called *after* validity has changed.
+      if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
+        UpdateBarredFromConstraintValidation();
+      }
+
       states |= NS_EVENT_STATE_REQUIRED | NS_EVENT_STATE_OPTIONAL |
                 NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID;
     } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
       states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID;
     } else if (aName == nsGkAtoms::pattern) {
       UpdatePatternMismatchValidityState();
       states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID;
@@ -3202,16 +3211,20 @@ nsHTMLInputElement::IntrinsicState() con
       GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
     }
 
     if (value.IsEmpty()) {
       state |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
     }
   }
 
+  if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
+    state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
+  }
+
   return state;
 }
 
 PRBool
 nsHTMLInputElement::RestoreState(nsPresState* aState)
 {
   PRBool restoredCheckedState = PR_FALSE;
 
@@ -3761,23 +3774,24 @@ nsHTMLInputElement::UpdateAllValiditySta
     if (doc) {
       MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
       doc->ContentStatesChanged(this, nsnull,
                                 NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID);
     }
   }
 }
 
-PRBool
-nsHTMLInputElement::IsBarredFromConstraintValidation() const
+void
+nsHTMLInputElement::UpdateBarredFromConstraintValidation()
 {
-  return mType == NS_FORM_INPUT_HIDDEN ||
-         mType == NS_FORM_INPUT_BUTTON ||
-         mType == NS_FORM_INPUT_RESET ||
-         HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+  SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
+                                    mType == NS_FORM_INPUT_BUTTON ||
+                                    mType == NS_FORM_INPUT_RESET ||
+                                    HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
+                                    HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
 }
 
 nsresult
 nsHTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
                                          ValidityStateType aType)
 {
   nsresult rv = NS_OK;
 
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -268,17 +268,17 @@ public:
   PRBool   IsValueMissing();
   PRBool   HasTypeMismatch();
   PRBool   HasPatternMismatch();
   void     UpdateTooLongValidityState();
   void     UpdateValueMissingValidityState();
   void     UpdateTypeMismatchValidityState();
   void     UpdatePatternMismatchValidityState();
   void     UpdateAllValidityStates(PRBool aNotify);
-  PRBool   IsBarredFromConstraintValidation() const;
+  void     UpdateBarredFromConstraintValidation();
   nsresult GetValidationMessage(nsAString& aValidationMessage,
                                 ValidityStateType aType);
 
 protected:
   // Pull IsSingleLineTextControl into our scope, otherwise it'd be hidden
   // by the nsITextControlElement version.
   using nsGenericHTMLFormElement::IsSingleLineTextControl;
 
--- a/content/html/content/src/nsHTMLObjectElement.cpp
+++ b/content/html/content/src/nsHTMLObjectElement.cpp
@@ -125,19 +125,16 @@ public:
   virtual PRUint32 GetCapabilities() const;
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   nsresult CopyInnerTo(nsGenericElement* aDest) const;
 
   void StartObjectLoad() { StartObjectLoad(PR_TRUE); }
 
-  // nsIConstraintValidation
-  PRBool IsBarredFromConstraintValidation() const { return PR_TRUE; }
-
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLObjectElement,
                                                      nsGenericHTMLFormElement)
 
   virtual nsXPCClassInfo* GetClassInfo();
 private:
   /**
    * Calls LoadObject with the correct arguments to start the plugin load.
    */
@@ -152,16 +149,19 @@ NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER
 
 nsHTMLObjectElement::nsHTMLObjectElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                                          PRUint32 aFromParser)
   : nsGenericHTMLFormElement(aNodeInfo),
     mIsDoneAddingChildren(!aFromParser)
 {
   RegisterFreezableElement();
   SetIsNetworkCreated(aFromParser == NS_FROM_PARSER_NETWORK);
+
+  // <object> is always barred from constraint validation.
+  SetBarredFromConstraintValidation(PR_TRUE);
 }
 
 nsHTMLObjectElement::~nsHTMLObjectElement()
 {
   UnregisterFreezableElement();
   DestroyImageLoadingContent();
 }
 
--- a/content/html/content/src/nsHTMLOutputElement.cpp
+++ b/content/html/content/src/nsHTMLOutputElement.cpp
@@ -78,19 +78,16 @@ public:
 
   PRBool ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
                         const nsAString& aValue, nsAttrValue& aResult);
 
   // This function is called when a callback function from nsIMutationObserver
   // has to be used to update the defaultValue attribute.
   void DescendantsChanged();
 
-  // nsIConstraintValidation
-  PRBool IsBarredFromConstraintValidation() const { return PR_TRUE; }
-
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLOutputElement,
                                                      nsGenericHTMLFormElement)
@@ -111,16 +108,18 @@ protected:
 NS_IMPL_NS_NEW_HTML_ELEMENT(Output)
 
 
 nsHTMLOutputElement::nsHTMLOutputElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLFormElement(aNodeInfo)
   , mValueModeFlag(eModeDefault)
 {
   AddMutationObserver(this);
+  // <output> is always barred from constraint validation.
+  SetBarredFromConstraintValidation(PR_TRUE);
 }
 
 nsHTMLOutputElement::~nsHTMLOutputElement()
 {
   if (mTokenList) {
     mTokenList->DropReference();
   }
 }
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -1317,16 +1317,36 @@ nsHTMLSelectElement::BeforeSetAttr(PRInt
     mDisabledChanged = PR_TRUE;
   }
 
   return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
                                                  aValue, aNotify);
 }
 
 nsresult
+nsHTMLSelectElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
+                                  const nsAString* aValue, PRBool aNotify)
+{
+  if (aName == nsGkAtoms::disabled && aNameSpaceID == kNameSpaceID_None) {
+    SetBarredFromConstraintValidation(!!aValue);
+    if (aNotify) {
+      nsIDocument* doc = GetCurrentDoc();
+      if (doc) {
+        MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
+        doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_VALID |
+                                                NS_EVENT_STATE_INVALID);
+      }
+    }
+  }
+
+  return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
+                                                aValue, aNotify);
+}
+
+nsresult
 nsHTMLSelectElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
                                PRBool aNotify)
 {
   if (aNotify && aNameSpaceID == kNameSpaceID_None &&
       aAttribute == nsGkAtoms::multiple) {
     // We're changing from being a multi-select to a single-select.
     // Make sure we only have one option selected before we do that.
     // Note that this needs to come before we really unset the attr,
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -282,16 +282,18 @@ public:
   // nsISelectElement
   NS_DECL_NSISELECTELEMENT
 
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                  const nsAString* aValue, PRBool aNotify);
+  virtual nsresult AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
+                                const nsAString* aValue, PRBool aNotify);
   virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
                              PRBool aNotify);
   
   virtual nsresult DoneAddingChildren(PRBool aHaveNotified);
   virtual PRBool IsDoneAddingChildren();
 
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -203,17 +203,17 @@ public:
 
   virtual nsXPCClassInfo* GetClassInfo();
 
   // nsIConstraintValidation
   PRBool   IsTooLong();
   PRBool   IsValueMissing() const;
   void     UpdateTooLongValidityState();
   void     UpdateValueMissingValidityState();
-  PRBool   IsBarredFromConstraintValidation() const;
+  void     UpdateBarredFromConstraintValidation();
   nsresult GetValidationMessage(nsAString& aValidationMessage,
                                 ValidityStateType aType);
 
 protected:
   using nsGenericHTMLFormElement::IsSingleLineTextControl; // get rid of the compiler warning
 
   nsCOMPtr<nsIControllers> mControllers;
   /** Whether or not the value has changed since its default value was given. */
@@ -1071,17 +1071,24 @@ nsHTMLTextAreaElement::AfterSetAttr(PRIn
                                     const nsAString* aValue, PRBool aNotify)
 {
   PRInt32 states = 0;
 
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
         aName == nsGkAtoms::readonly) {
       UpdateValueMissingValidityState();
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID;
+
+      // This *has* to be called *after* validity has changed.
+      if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
+        UpdateBarredFromConstraintValidation();
+      }
+
+      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
+                NS_EVENT_STATE_MOZ_SUBMITINVALID;
     } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
       states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID;
     }
 
     if (aNotify) {
       nsIDocument* doc = GetCurrentDoc();
 
@@ -1175,20 +1182,23 @@ nsHTMLTextAreaElement::UpdateTooLongVali
 }
 
 void
 nsHTMLTextAreaElement::UpdateValueMissingValidityState()
 {
   SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
 }
 
-PRBool
-nsHTMLTextAreaElement::IsBarredFromConstraintValidation() const
+void
+nsHTMLTextAreaElement::UpdateBarredFromConstraintValidation()
 {
-  return HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+  SetBarredFromConstraintValidation(HasAttr(kNameSpaceID_None,
+                                            nsGkAtoms::readonly) ||
+                                    HasAttr(kNameSpaceID_None,
+                                            nsGkAtoms::disabled));
 }
 
 nsresult
 nsHTMLTextAreaElement::GetValidationMessage(nsAString& aValidationMessage,
                                             ValidityStateType aType)
 {
   nsresult rv = NS_OK;
 
--- a/content/html/content/src/nsIConstraintValidation.cpp
+++ b/content/html/content/src/nsIConstraintValidation.cpp
@@ -36,21 +36,25 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIConstraintValidation.h"
 
 #include "nsAString.h"
 #include "nsGenericHTMLElement.h"
 #include "nsHTMLFormElement.h"
 #include "nsDOMValidityState.h"
+#include "nsIFormControl.h"
+#include "nsHTMLFormElement.h"
 
 
 nsIConstraintValidation::nsIConstraintValidation()
   : mValidityBitField(0)
   , mValidity(nsnull)
+  // By default, all elements are subjects to constraint validation.
+  , mBarredFromConstraintValidation(PR_FALSE)
 {
 }
 
 nsIConstraintValidation::~nsIConstraintValidation()
 {
   if (mValidity) {
     mValidity->Disconnect();
   }
@@ -111,39 +115,63 @@ nsIConstraintValidation::CheckValidity(P
   NS_ASSERTION(content, "This class should be inherited by HTML elements only!");
 
   return nsContentUtils::DispatchTrustedEvent(content->GetOwnerDoc(), content,
                                               NS_LITERAL_STRING("invalid"),
                                               PR_FALSE, PR_TRUE);
 }
 
 void
+nsIConstraintValidation::SetValidityState(ValidityStateType aState,
+                                          PRBool aValue)
+{
+  PRBool previousValidity = IsValid();
+
+  if (aValue) {
+    mValidityBitField |= aState;
+  } else {
+    mValidityBitField &= ~aState;
+  }
+
+  // Inform the form element if our validity has changed.
+  if (previousValidity != IsValid() && IsCandidateForConstraintValidation()) {
+    nsCOMPtr<nsIFormControl> formCtrl = do_QueryInterface(this);
+    NS_ASSERTION(formCtrl, "This interface should be used by form elements!");
+
+    nsHTMLFormElement* form =
+      static_cast<nsHTMLFormElement*>(formCtrl->GetFormElement());
+    if (form) {
+      form->UpdateValidity(IsValid());
+    }
+  }
+}
+
+void
 nsIConstraintValidation::SetCustomValidity(const nsAString& aError)
 {
   mCustomValidity.Assign(aError);
   SetValidityState(VALIDITY_STATE_CUSTOM_ERROR, !mCustomValidity.IsEmpty());
 }
 
-PRBool
-nsIConstraintValidation::IsCandidateForConstraintValidation() const
+void
+nsIConstraintValidation::SetBarredFromConstraintValidation(PRBool aBarred)
 {
-  /**
-   * An element is never candidate for constraint validation if:
-   * - it is disabled ;
-   * - TODO: or it's ancestor is a datalist element (bug 555840).
-   * We are doing these checks here to prevent writing them in every
-   * |IsBarredFromConstraintValidation| function.
-   */
+  PRBool previousBarred = mBarredFromConstraintValidation;
+
+  mBarredFromConstraintValidation = aBarred;
 
-  nsCOMPtr<nsIContent> content =
-    do_QueryInterface(const_cast<nsIConstraintValidation*>(this));
-  NS_ASSERTION(content, "This class should be inherited by HTML elements only!");
+  // Inform the form element if our status regarding constraint validation
+  // is going to change.
+  if (!IsValid() && previousBarred != mBarredFromConstraintValidation) {
+    nsCOMPtr<nsIFormControl> formCtrl = do_QueryInterface(this);
+    NS_ASSERTION(formCtrl, "This interface should be used by form elements!");
 
-  // For the moment, all elements that are not barred from constraint validation
-  // accept the disabled attribute and elements that are always barred from
-  // constraint validation do not accept it (objects, fieldset, output).
-  // If one of these elements change and become not always barred from
-  // constraint validation or another element appear with constraint validation
-  // support and can't be disabled, this code will have to be changed.
-  return !content->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled) &&
-         !IsBarredFromConstraintValidation();
+    nsHTMLFormElement* form =
+      static_cast<nsHTMLFormElement*>(formCtrl->GetFormElement());
+    if (form) {
+      // If the element is going to be barred from constraint validation,
+      // we can inform the form that we are now valid.
+      // Otherwise, we are now invalid.
+      form->UpdateValidity(aBarred);
+    }
+  }
 }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/add-invalid-element.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <input id='i' type='email' value='foo'>
+    <form>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/add-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].appendChild(document.getElementById('b'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <button id='b' type='submit'></button>
+    <form>
+      <input required>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/change-type-not-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('b').type = 'button';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <button id='b' type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/change-type-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('b').type = 'submit';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <button id='b'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/dynamic-invalid-barred-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = '';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' value='foo' required readonly>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/dynamic-invalid-barred.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').readOnly = 'ro';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/dynamic-invalid-not-barred.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').removeAttribute('readonly');
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required readonly>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/dynamic-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = '';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' value='foo' required>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/dynamic-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = 'foo';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/invalid-barred-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required readonly>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/invalid-ref-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input type='email' value='foo'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/invalid-ref-3.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <table id='t'>
+     <form>
+       <tr><td><input required></td></tr>
+       <tr><td><button type='submit'></button></td></tr>
+     </form>
+   </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/invalid-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/reftest.list
@@ -0,0 +1,16 @@
+== static-valid.html valid-ref.html
+== dynamic-valid.html valid-ref.html
+== static-invalid.html invalid-ref.html
+== dynamic-invalid.html invalid-ref.html
+== dynamic-invalid-barred.html invalid-barred-ref.html
+== dynamic-invalid-barred-2.html invalid-barred-ref.html
+== dynamic-invalid-not-barred.html invalid-ref.html
+== static-invalid-barred.html invalid-barred-ref.html
+== remove-invalid-element.html valid-ref-2.html
+== add-invalid-element.html invalid-ref-2.html
+== add-submit-control.html invalid-ref.html
+== remove-submit-control.html valid-ref-3.html
+== change-type-submit-control.html invalid-ref.html
+== change-type-not-submit-control.html valid-ref-4.html
+== self-invalid.html about:blank
+== remove-form.html invalid-ref-3.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/remove-form.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('t').removeChild(document.forms[0]);
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <table id='t'>
+      <form>
+        <tr><td><input required></td></tr>
+        <tr><td><button type='submit'></button></td></tr>
+      </form>
+    </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/remove-invalid-element.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].removeChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' type='email' value='foo'>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/remove-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.body.appendChild(document.getElementById('b'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <button id='b' type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/self-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('b').setCustomValidity('foo');
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <button id='b' type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/static-invalid-barred.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input required readonly>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/static-invalid.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input required>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/static-valid.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input value='foo' required>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/valid-ref-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/valid-ref-3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+    </form>
+    <button type='submit'></button>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/valid-ref-4.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+      <button></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/button-submit/valid-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input value='foo' required>
+      <button type='submit'></button>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/add-invalid-element.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <input id='i' type='email' value='foo'>
+    <form>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/add-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <input id='i' type='image'>
+    <form>
+      <input required>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/change-type-not-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').type = 'text';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <input id='i' type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/change-type-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').type = 'image';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <input id='i'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/dynamic-invalid-barred-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = '';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' value='foo' required readonly>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/dynamic-invalid-barred.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').readOnly = 'ro';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/dynamic-invalid-not-barred.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').removeAttribute('readonly');
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required readonly>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/dynamic-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = '';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' value='foo' required>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/dynamic-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = 'foo';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/invalid-barred-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required readonly>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/invalid-ref-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input type='email' value='foo'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/invalid-ref-3.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <table id='t'>
+     <form>
+       <tr><td><input required></td></tr>
+       <tr><td><input type='image'></td></tr>
+     </form>
+   </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/invalid-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/reftest.list
@@ -0,0 +1,16 @@
+== static-valid.html valid-ref.html
+== dynamic-valid.html valid-ref.html
+== static-invalid.html invalid-ref.html
+== dynamic-invalid.html invalid-ref.html
+== dynamic-invalid-barred.html invalid-barred-ref.html
+== dynamic-invalid-barred-2.html invalid-barred-ref.html
+== dynamic-invalid-not-barred.html invalid-ref.html
+== static-invalid-barred.html invalid-barred-ref.html
+== remove-invalid-element.html valid-ref-2.html
+== add-invalid-element.html invalid-ref-2.html
+== add-submit-control.html invalid-ref.html
+== remove-submit-control.html valid-ref-3.html
+== change-type-submit-control.html invalid-ref.html
+== change-type-not-submit-control.html valid-ref-4.html
+== self-invalid.html about:blank
+== remove-form.html invalid-ref-3.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/remove-form.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('t').removeChild(document.forms[0]);
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <table id='t'>
+      <form>
+        <tr><td><input required></td></tr>
+        <tr><td><input type='image'></td></tr>
+      </form>
+    </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/remove-invalid-element.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].removeChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' type='email' value='foo'>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/remove-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.body.appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <input id='i' type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/self-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').setCustomValidity('foo');
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/static-invalid-barred.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input required readonly>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/static-invalid.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input required>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/static-valid.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input value='foo' required>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/valid-ref-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/valid-ref-3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+    </form>
+    <input type='image'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/valid-ref-4.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+      <input>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-image/valid-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input value='foo' required>
+      <input type='image'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/add-invalid-element.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <input id='i' type='email' value='foo'>
+    <form>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/add-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <input id='i' type='submit'>
+    <form>
+      <input required>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/change-type-not-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').type = 'text';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <input id='i' type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/change-type-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').type = 'submit';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <input id='i'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/dynamic-invalid-barred-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = '';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' value='foo' required readonly>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/dynamic-invalid-barred.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').readOnly = 'ro';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/dynamic-invalid-not-barred.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').removeAttribute('readonly');
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required readonly>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/dynamic-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = '';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' value='foo' required>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/dynamic-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').value = 'foo';
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' required>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/invalid-barred-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required readonly>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/invalid-ref-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input type='email' value='foo'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/invalid-ref-3.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <table id='t'>
+     <form>
+       <tr><td><input required></td></tr>
+       <tr><td><input type='submit'></td></tr>
+     </form>
+   </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/invalid-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/reftest.list
@@ -0,0 +1,16 @@
+== static-valid.html valid-ref.html
+== dynamic-valid.html valid-ref.html
+== static-invalid.html invalid-ref.html
+== dynamic-invalid.html invalid-ref.html
+== dynamic-invalid-barred.html invalid-barred-ref.html
+== dynamic-invalid-barred-2.html invalid-barred-ref.html
+== dynamic-invalid-not-barred.html invalid-ref.html
+== static-invalid-barred.html invalid-barred-ref.html
+== remove-invalid-element.html valid-ref-2.html
+== add-invalid-element.html invalid-ref-2.html
+== add-submit-control.html invalid-ref.html
+== remove-submit-control.html valid-ref-3.html
+== change-type-submit-control.html invalid-ref.html
+== change-type-not-submit-control.html valid-ref-4.html
+== self-invalid.html about:blank
+== remove-form.html invalid-ref-3.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/remove-form.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('t').removeChild(document.forms[0]);
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <table id='t'>
+      <form>
+        <tr><td><input required></td></tr>
+        <tr><td><input type='submit'></td></tr>
+      </form>
+    </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/remove-invalid-element.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.forms[0].removeChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' type='email' value='foo'>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/remove-submit-control.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.body.appendChild(document.getElementById('i'));
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input required>
+      <input id='i' type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/self-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <script>
+    function onloadHandler()
+    {
+      document.getElementById('i').setCustomValidity('foo');
+      document.documentElement.className = '';
+    }
+  </script>
+  <body onload='onloadHandler();'>
+    <form>
+      <input id='i' type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/static-invalid-barred.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input required readonly>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/static-invalid.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input required>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/static-valid.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-submit-invalid { display: none; }
+    </style>
+  </head>
+  <body>
+    <form>
+      <input value='foo' required>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/valid-ref-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/valid-ref-3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+    </form>
+    <input type='submit'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/valid-ref-4.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input required>
+      <input>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/input-submit/valid-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <form>
+      <input value='foo' required>
+      <input type='submit'>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-submit-invalid/reftest.list
@@ -0,0 +1,3 @@
+include button-submit/reftest.list
+include input-image/reftest.list
+include input-submit/reftest.list
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -75,16 +75,19 @@ include css-required/reftest.list
 include css-optional/reftest.list
 
 # css valid
 include css-valid/reftest.list
 
 # css invalid
 include css-invalid/reftest.list
 
+# css-submit-invalid
+include css-submit-invalid/reftest.list
+
 # css transitions
 include css-transitions/reftest.list
 
 # css values and units
 include css-valuesandunits/reftest.list
 
 # Reftests in css-visited are run using
 # layout/style/test/test_visited_reftests instead of using the reftest
--- a/layout/style/nsCSSPseudoClassList.h
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -171,13 +171,15 @@ CSS_STATE_PSEUDO_CLASS(inRange, ":in-ran
 CSS_STATE_PSEUDO_CLASS(outOfRange, ":out-of-range", NS_EVENT_STATE_OUTOFRANGE)
 CSS_STATE_PSEUDO_CLASS(defaultPseudo, ":default", NS_EVENT_STATE_DEFAULT)
 CSS_STATE_PSEUDO_CLASS(mozReadOnly, ":-moz-read-only",
                        NS_EVENT_STATE_MOZ_READONLY)
 CSS_STATE_PSEUDO_CLASS(mozReadWrite, ":-moz-read-write",
                        NS_EVENT_STATE_MOZ_READWRITE)
 CSS_STATE_PSEUDO_CLASS(mozPlaceholder, ":-moz-placeholder",
                        NS_EVENT_STATE_MOZ_PLACEHOLDER)
+CSS_STATE_PSEUDO_CLASS(mozSubmitInvalid, ":-moz-submit-invalid",
+                       NS_EVENT_STATE_MOZ_SUBMITINVALID)
 
 #ifdef DEFINED_CSS_STATE_PSEUDO_CLASS
 #undef DEFINED_CSS_STATE_PSEUDO_CLASS
 #undef CSS_STATE_PSEUDO_CLASS
 #endif