Bug 610687 - Make all the radio button group suffering from being missing instead of only radio's with the required attribute. r+a=sicking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Thu, 16 Dec 2010 11:51:59 -0800
changeset 59452 ea00eee207e80a64af666f93e9d32d0dd9af1196
parent 59451 dc8dea3241427c47d6dff6e51f2db02240e3f430
child 59453 5a00160adc4e94611f98e497036d6ec061089def
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)
bugs610687
milestone2.0b9pre
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 610687 - Make all the radio button group suffering from being missing instead of only radio's with the required attribute. r+a=sicking
content/html/content/public/nsIConstraintValidation.h
content/html/content/public/nsIRadioVisitor.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/test/Makefile.in
content/html/content/test/test_bug345822.html
content/html/content/test/test_bug546995.html
content/html/content/test/test_bug610687.html
layout/reftests/bugs/557087-1.html
layout/reftests/bugs/557087-2.html
layout/reftests/css-invalid/input/input-radio-customerror.html
layout/reftests/css-invalid/input/input-radio-dyn-valid-1.html
layout/reftests/css-invalid/input/input-radio-dyn-valid-2.html
layout/reftests/css-invalid/input/input-radio-nogroup-required-invalid.html
layout/reftests/css-invalid/input/input-radio-nogroup-required-valid.html
layout/reftests/css-invalid/input/input-radio-required.html
layout/reftests/css-invalid/input/reftest.list
layout/reftests/css-invalid/input/success-ref.html
layout/reftests/css-ui-invalid/input/input-radio-customerror.html
layout/reftests/css-ui-invalid/input/input-radio-dyn-valid-1.html
layout/reftests/css-ui-invalid/input/input-radio-dyn-valid-2.html
layout/reftests/css-ui-invalid/input/input-radio-nogroup-required-invalid.html
layout/reftests/css-ui-invalid/input/input-radio-nogroup-required-valid.html
layout/reftests/css-ui-invalid/input/input-radio-required-invalid-changed-2.html
layout/reftests/css-ui-invalid/input/input-radio-required-invalid-default-2.html
layout/reftests/css-ui-invalid/input/input-radio-required.html
layout/reftests/css-ui-invalid/input/reftest.list
layout/reftests/css-ui-valid/input/input-radio-customerror.html
layout/reftests/css-ui-valid/input/input-radio-dyn-valid-1.html
layout/reftests/css-ui-valid/input/input-radio-dyn-valid-2.html
layout/reftests/css-ui-valid/input/input-radio-nogroup-required-invalid.html
layout/reftests/css-ui-valid/input/input-radio-nogroup-required-valid.html
layout/reftests/css-ui-valid/input/input-radio-required.html
layout/reftests/css-ui-valid/input/reftest.list
layout/reftests/css-valid/input/input-radio-customerror.html
layout/reftests/css-valid/input/input-radio-dyn-valid-1.html
layout/reftests/css-valid/input/input-radio-dyn-valid-2.html
layout/reftests/css-valid/input/input-radio-nogroup-required-invalid.html
layout/reftests/css-valid/input/input-radio-nogroup-required-valid.html
layout/reftests/css-valid/input/input-radio-required.html
layout/reftests/css-valid/input/reftest.list
layout/reftests/css-valid/input/success-ref.html
--- a/content/html/content/public/nsIConstraintValidation.h
+++ b/content/html/content/public/nsIConstraintValidation.h
@@ -71,44 +71,44 @@ public:
   PRBool IsValid() const { return mValidityBitField == 0; }
 
   PRBool IsCandidateForConstraintValidation() const {
            return !mBarredFromConstraintValidation;
          }
 
   NS_IMETHOD GetValidationMessage(nsAString& aValidationMessage);
 
-protected:
-
   enum ValidityStateType
   {
     VALIDITY_STATE_VALUE_MISSING    = 0x01, // 0b00000001
     VALIDITY_STATE_TYPE_MISMATCH    = 0x02, // 0b00000010
     VALIDITY_STATE_PATTERN_MISMATCH = 0x04, // 0b00000100
     VALIDITY_STATE_TOO_LONG         = 0x08, // 0b00001000
     VALIDITY_STATE_RANGE_UNDERFLOW  = 0x10, // 0b00010000
     VALIDITY_STATE_RANGE_OVERFLOW   = 0x20, // 0b00100000
     VALIDITY_STATE_STEP_MISMATCH    = 0x40, // 0b01000000
     VALIDITY_STATE_CUSTOM_ERROR     = 0x80  // 0b10000000
   };
 
+  void SetValidityState(ValidityStateType mState,
+                        PRBool mValue);
+
+protected:
+
   // You can't instantiate an object from that class.
   nsIConstraintValidation();
 
   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);
-
   void SetBarredFromConstraintValidation(PRBool aBarred);
 
   virtual nsresult GetValidationMessage(nsAString& aValidationMessage,
                                         ValidityStateType aType) {
                      return NS_OK;
                    }
 
 private:
--- a/content/html/content/public/nsIRadioVisitor.h
+++ b/content/html/content/public/nsIRadioVisitor.h
@@ -36,16 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsIRadioVisitor_h___
 #define nsIRadioVisitor_h___
 
 #include "nsISupports.h"
 class nsIFormControl;
+class nsIDocument;
 
 // IID for the nsIRadioControl interface
 #define NS_IRADIOVISITOR_IID \
 { 0xd3494bd2, 0x1dd1, 0x11b2, \
   { 0xbe, 0x86, 0xb5, 0x08, 0xc8, 0x71, 0xd7, 0xc5 } }
 
 /**
  * This interface is used for the text control frame to store its value away
@@ -99,9 +100,35 @@ NS_GetRadioGetCheckedChangedVisitor(PRBo
 
 /**
  * This visitor will make sure all radio into the group updates their
  * value missing validity state.
  */
 nsIRadioVisitor*
 NS_GetRadioUpdateValueMissingVisitor();
 
+/**
+ * This visitor will return (via aRequired) if an element of the group has the
+ * required attribute set.
+ *
+ * @param aExcludeElement an element to exclude (for optimization purpose), can be null
+ * @param aRequired       whether there is a radio in the group with the required attribute [OUT]
+ * @return the visitor
+ */
+nsIRadioVisitor*
+NS_GetRadioGroupRequiredVisitor(nsIFormControl* aExcludeElement,
+                                bool* aRequired);
+
+/**
+ * This visitor will update the validity states of all radio in the group and
+ * call ContentStatesChanged if needed.
+ *
+ * @param aExcludeElement an element to exclude (for optimization purpose), can be null
+ * @param aDocument       the document owning the group
+ * @param aNotify         whether we should call ContentStatesChanged
+ * @return the visitor
+ */
+nsIRadioVisitor*
+NS_SetRadioValueMissingState(nsIFormControl* aExcludeElement,
+                             nsIDocument* aDocument,
+                             bool aValidity, bool aNotify);
+
 #endif // nsIRadioVisitor_h___
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -3501,36 +3501,33 @@ nsHTMLInputElement::WillRemoveFromRadioG
       }
       gotName = PR_TRUE;
     }
 
     nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
     if (container) {
       container->SetCurrentRadioButton(name, nsnull);
     }
-
-    // Removing a checked radio from the group can change the validity state.
-    // Let's ask other radio to update their value missing validity state.
-    nsCOMPtr<nsIRadioVisitor> visitor =
-      NS_GetRadioUpdateValueMissingVisitor();
-    VisitGroup(visitor, PR_FALSE);
   }
-  
+
   //
   // Remove this radio from its group in the container
   //
   nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
   if (container) {
     if (!gotName) {
       if (!GetNameIfExists(name)) {
         // If the name doesn't exist, nothing is going to happen anyway
         return;
       }
       gotName = PR_TRUE;
     }
+
+    UpdateValueMissingValidityStateForRadio(true);
+
     container->RemoveFromRadioGroup(name,
                                     static_cast<nsIFormControl*>(this));
   }
 }
 
 PRBool
 nsHTMLInputElement::IsHTMLFocusable(PRBool aWithMouse, PRBool *aIsFocusable, PRInt32 *aTabIndex)
 {
@@ -3803,21 +3800,16 @@ nsHTMLInputElement::IsValueMissing()
 
     return value.IsEmpty();
   }
 
   switch (mType)
   {
     case NS_FORM_INPUT_CHECKBOX:
       return !GetChecked();
-    case NS_FORM_INPUT_RADIO:
-      {
-        nsCOMPtr<nsIDOMHTMLInputElement> selected = GetSelectedRadioButton();
-        return !selected;
-      }
     case NS_FORM_INPUT_FILE:
       {
         const nsCOMArray<nsIDOMFile>& files = GetFiles();
         return !files.Count();
       }
     default:
       return PR_FALSE;
   }
@@ -3891,18 +3883,53 @@ nsHTMLInputElement::UpdateTooLongValidit
 {
   // TODO: this code will be re-enabled with bug 613016 and bug 613019.
 #if 0
   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
 #endif
 }
 
 void
+nsHTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
+{
+  PRBool notify = !GET_BOOLBIT(mBitField, BF_PARSER_CREATING);
+  nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
+  // If there is no selection, that might mean the radio is not in a group.
+  // In that case, we can look for the checked state of the radio.
+  bool selected = selection ? true
+                            : aIgnoreSelf ? false : GetChecked();
+  bool required = aIgnoreSelf ? false
+                              : HasAttr(kNameSpaceID_None, nsGkAtoms::required);
+  bool valueMissing = false;
+
+  // If the current radio is required, don't check the entire group.
+  if (!required) {
+    nsCOMPtr<nsIRadioVisitor> visitor =
+      NS_GetRadioGroupRequiredVisitor(this, &required);
+    VisitGroup(visitor, notify);
+  }
+
+  valueMissing = required && !selected;
+
+  SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
+
+  nsCOMPtr<nsIRadioVisitor> visitor =
+    NS_SetRadioValueMissingState(this, GetCurrentDoc(), valueMissing,
+                                 notify);
+  VisitGroup(visitor, notify);
+}
+
+void
 nsHTMLInputElement::UpdateValueMissingValidityState()
 {
+  if (mType == NS_FORM_INPUT_RADIO) {
+    UpdateValueMissingValidityStateForRadio(false);
+    return;
+  }
+
   SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
 }
 
 void
 nsHTMLInputElement::UpdateTypeMismatchValidityState()
 {
     SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
 }
@@ -4251,16 +4278,84 @@ public:
      */
     nsCOMPtr<nsITextControlElement> textCtl(do_QueryInterface(aRadio));
     NS_ASSERTION(textCtl, "Visit() passed a null or non-radio pointer");
     textCtl->OnValueChanged(PR_TRUE);
     return NS_OK;
   }
 };
 
+class nsRadioGroupRequiredVisitor : public nsRadioVisitor {
+public:
+  nsRadioGroupRequiredVisitor(nsIFormControl* aExcludeElement, bool* aRequired)
+    : mRequired(aRequired)
+    , mExcludeElement(aExcludeElement)
+    { }
+
+  NS_IMETHOD Visit(nsIFormControl* aRadio, PRBool* aStop)
+  {
+    if (aRadio == mExcludeElement) {
+      return NS_OK;
+    }
+
+    *mRequired = static_cast<nsHTMLInputElement*>(aRadio)
+      ->HasAttr(kNameSpaceID_None, nsGkAtoms::required);
+
+    if (*mRequired) {
+      *aStop = PR_TRUE;
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  bool* mRequired;
+  nsIFormControl* mExcludeElement;
+};
+
+class nsRadioSetValueMissingState : public nsRadioVisitor {
+public:
+  nsRadioSetValueMissingState(nsIFormControl* aExcludeElement,
+                              nsIDocument* aDocument, bool aValidity,
+                              bool aNotify)
+    : mExcludeElement(aExcludeElement)
+    , mDocument(aDocument)
+    , mValidity(aValidity)
+    , mNotify(aNotify)
+    { }
+
+  NS_IMETHOD Visit(nsIFormControl* aRadio, PRBool* aStop)
+  {
+    if (aRadio == mExcludeElement) {
+      return NS_OK;
+    }
+
+    nsHTMLInputElement* input = static_cast<nsHTMLInputElement*>(aRadio);
+
+    input->SetValidityState(nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING,
+                            mValidity);
+
+    if (mNotify && mDocument) {
+      mDocument->ContentStatesChanged(input, nsnull,
+                                      NS_EVENT_STATE_VALID |
+                                      NS_EVENT_STATE_INVALID |
+                                      NS_EVENT_STATE_MOZ_UI_VALID |
+                                      NS_EVENT_STATE_MOZ_UI_INVALID);
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  nsIFormControl* mExcludeElement;
+  nsIDocument* mDocument;
+  bool mValidity;
+  bool mNotify;
+};
+
 nsresult
 NS_GetRadioSetCheckedChangedVisitor(PRBool aCheckedChanged,
                                     nsIRadioVisitor** aVisitor)
 {
   //
   // These are static so that we don't have to keep creating new visitors for
   // such an ordinary process all the time.  There are only two possibilities
   // for this visitor: set to true, and set to false.
@@ -4337,16 +4432,32 @@ NS_GetRadioGetCheckedChangedVisitor(PRBo
  * See bug 586298
  */
 nsIRadioVisitor*
 NS_GetRadioUpdateValueMissingVisitor()
 {
   return new nsRadioUpdateValueMissingVisitor();
 }
 
+nsIRadioVisitor*
+NS_GetRadioGroupRequiredVisitor(nsIFormControl* aExcludeElement,
+                                bool* aRequired)
+{
+  return new nsRadioGroupRequiredVisitor(aExcludeElement, aRequired);
+}
+
+nsIRadioVisitor*
+NS_SetRadioValueMissingState(nsIFormControl* aExcludeElement,
+                             nsIDocument* aDocument,
+                             bool aValidity, bool aNotify)
+{
+  return new nsRadioSetValueMissingState(aExcludeElement, aDocument, aValidity,
+                                         aNotify);
+}
+
 NS_IMETHODIMP_(PRBool)
 nsHTMLInputElement::IsSingleLineTextControl() const
 {
   return IsSingleLineTextControl(PR_FALSE);
 }
 
 NS_IMETHODIMP_(PRBool)
 nsHTMLInputElement::IsTextArea() const
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -105,17 +105,16 @@ public:
    */
   nsresult StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile);
 private:
   // Directories are stored here during private browsing mode
   nsInterfaceHashtable<nsStringHashKey, nsILocalFile> mUploadLastDirStore;
   PRBool mInPrivateBrowsing;
 };
 
-class nsIRadioGroupContainer;
 class nsIRadioVisitor;
 
 class nsHTMLInputElement : public nsGenericHTMLFormElement,
                            public nsImageLoadingContent,
                            public nsIDOMHTMLInputElement,
                            public nsITextControlElement,
                            public nsIPhonetic,
                            public nsIDOMNSEditableElement,
@@ -278,16 +277,25 @@ public:
   void     UpdateTooLongValidityState();
   void     UpdateValueMissingValidityState();
   void     UpdateTypeMismatchValidityState();
   void     UpdatePatternMismatchValidityState();
   void     UpdateAllValidityStates(PRBool aNotify);
   void     UpdateBarredFromConstraintValidation();
   nsresult GetValidationMessage(nsAString& aValidationMessage,
                                 ValidityStateType aType);
+  /**
+   * Update the value missing validity state for radio elements when they have
+   * a group.
+   *
+   * @param aIgnoreSelf Whether the required attribute and the checked state
+   * of the current radio should be ignored.
+   * @note This method shouldn't be called if the radio elemnet hasn't a group.
+   */
+  void     UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf);
 
   /**
    * Returns the filter which should be used for the file picker according to
    * the accept attribute value.
    *
    * See:
    * http://dev.w3.org/html5/spec/forms.html#attr-input-accept
    *
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -243,12 +243,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug605124-2.html \
 		test_bug605125-1.html \
 		test_bug605125-2.html \
 		test_bug612730.html \
 		test_bug613722.html \
 		test_bug613979.html \
 		test_bug615833.html \
 		test_bug601030.html \
+		test_bug610687.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/content/html/content/test/test_bug345822.html
+++ b/content/html/content/test/test_bug345822.html
@@ -253,23 +253,23 @@ function checkInputRequiredValidityForRa
   // The other radio button should not be disabled.
   // A disabled checked radio button in the radio group
   // is enough to not suffer from value missing.
   element2.checked = true;
   element2.disabled = true;
   checkNotSufferingFromBeingMissing(element);
 
   // If a radio button is not required but another radio button is required in
-  // the same group, the not required radio button should not suffer from value
+  // the same group, the not required radio button should suffer from value
   // missing.
   element2.disabled = false;
   element2.checked = false;
   element.required = false;
   element2.required = true;
-  checkNotSufferingFromBeingMissing(element);
+  checkSufferingFromBeingMissing(element);
   checkSufferingFromBeingMissing(element2);
 
   element.checked = true;
   checkNotSufferingFromBeingMissing(element2);
 
   // The checked radio is not in the group anymore, element2 should be invalid.
   document.forms[0].removeChild(element);
   checkSufferingFromBeingMissing(element2);
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug610687.html
@@ -0,0 +1,154 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=610687
+-->
+<head>
+  <title>Test for Bug 610687</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=610687">Mozilla Bug 610687</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <form>
+    <input type='radio' name='a'>
+    <input type='radio' name='a'>
+    <input type='radio' name='b'>
+  </form>
+  <input type='radio' name='a'>
+  <input type='radio' name='a'>
+  <input type='radio' name='b'>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 610687 **/
+
+function checkPseudoClasses(aElement, aValid, aValidUI, aInvalidUI)
+{
+  if (aValid) {
+    ok(aElement.mozMatchesSelector(":valid"), ":valid should apply");
+  } else {
+    ok(aElement.mozMatchesSelector(":invalid"), ":invalid should apply");
+  }
+
+  is(aElement.mozMatchesSelector(":-moz-ui-valid"), aValidUI,
+     aValid ? ":-moz-ui-valid should apply" : ":-moz-ui-valid should not apply");
+
+  is(aElement.mozMatchesSelector(":-moz-ui-invalid"), aInvalidUI,
+     aInvalidUI ? ":-moz-ui-invalid should apply" : ":-moz-ui-invalid should not apply");
+
+  if (aInvalidUI && (aValid || aValidUI)) {
+    ok(false, ":invalid can't apply with :valid or :-moz-valid-ui");
+  }
+}
+
+/**
+ * r1 and r2 should be in the same group.
+ * r3 should be in another group.
+ * form can be null.
+ */
+function checkRadios(r1, r2, r3, form)
+{
+  // Default state.
+  checkPseudoClasses(r1, true, false, false);
+  checkPseudoClasses(r2, true, false, false);
+  checkPseudoClasses(r3, true, false, false);
+
+  // Suffering from being missing (without ui-invalid).
+  r1.required = true;
+  checkPseudoClasses(r1, false, false, false);
+  checkPseudoClasses(r2, false, false, false);
+  checkPseudoClasses(r3, true, false, false);
+
+  // Suffering from being missing (with ui-invalid).
+  r1.checked = false;
+  checkPseudoClasses(r1, false, false, true);
+  checkPseudoClasses(r2, false, false, true);
+  checkPseudoClasses(r3, true, false, false);
+
+  // Do not suffer from being missing (with ui-valid).
+  r1.checked = true;
+  checkPseudoClasses(r1, true, true, false);
+  checkPseudoClasses(r2, true, true, false);
+  checkPseudoClasses(r3, true, false, false);
+
+  // Do not suffer from being missing (with ui-valid).
+  r1.checked = false;
+  r1.required = false;
+  checkPseudoClasses(r1, true, true, false);
+  checkPseudoClasses(r2, true, true, false);
+  checkPseudoClasses(r3, true, false, false);
+
+  // Suffering from being missing (with ui-invalid) with required set on one radio
+  // and the checked state changed on another.
+  r1.required = true;
+  r2.checked = false;
+  checkPseudoClasses(r1, false, false, true);
+  checkPseudoClasses(r2, false, false, true);
+  checkPseudoClasses(r3, true, false, false);
+
+  // Do not suffer from being missing (with ui-valid) by checking the radio which
+  // hasn't the required attribute.
+  r2.checked = true;
+  checkPseudoClasses(r1, true, true, false);
+  checkPseudoClasses(r2, true, true, false);
+  checkPseudoClasses(r3, true, false, false);
+
+  // .setCustomValidity() should not affect the entire group.
+  r1.checked = r2.checked = r3.checked = false;
+  r1.required = false;
+  r1.setCustomValidity('foo');
+  checkPseudoClasses(r1, false, false, true);
+  checkPseudoClasses(r2, true, true, false);
+  checkPseudoClasses(r3, true, true, false);
+
+  r1.setCustomValidity('');
+  r2.setCustomValidity('foo');
+  checkPseudoClasses(r1, true, true, false);
+  checkPseudoClasses(r2, false, false, true);
+  checkPseudoClasses(r3, true, true, false);
+
+  r2.setCustomValidity('');
+  r3.setCustomValidity('foo');
+  checkPseudoClasses(r1, true, true, false);
+  checkPseudoClasses(r2, true, true, false);
+  checkPseudoClasses(r3, false, false, true);
+
+  // Removing the radio with the required attribute should make the entire
+  // group invalid.
+  r1.setCustomValidity('');
+  r2.setCustomValidity('');
+  r1.required = false;
+  r2.required = true;
+  r1.checked = r2.checked = false;
+  checkPseudoClasses(r1, false, false, true);
+  checkPseudoClasses(r2, false, false, true);
+
+  var p = r2.parentNode;
+  p.removeChild(r2);
+  checkPseudoClasses(r1, true, true, false);
+  checkPseudoClasses(r2, false, false, true);
+
+  p.appendChild(r2);
+  checkPseudoClasses(r1, false, false, true);
+  checkPseudoClasses(r2, false, false, true);
+}
+
+var r1 = document.getElementsByTagName('input')[0];
+var r2 = document.getElementsByTagName('input')[1];
+var r3 = document.getElementsByTagName('input')[2];
+checkRadios(r1, r2, r3);
+
+r1 = document.getElementsByTagName('input')[3];
+r2 = document.getElementsByTagName('input')[4];
+r3 = document.getElementsByTagName('input')[5];
+checkRadios(r1, r2, r3);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/input-radio-customerror.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: if one radio in a group is suffering from a custom error, the other
+             radio should not be invalid. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').setCustomValidity('foo');
+                if (!document.getElementById('i1').mozMatchesSelector(':invalid') ||
+                    document.getElementById('i2').mozMatchesSelector(':invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio'>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/input-radio-dyn-valid-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: when there are no radio suffering from being missing in the radio
+             group, all radio should not suffer from being missing. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = true;
+                if (document.getElementById('i1').mozMatchesSelector(':invalid') ||
+                    document.getElementById('i2').mozMatchesSelector(':invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/input-radio-dyn-valid-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: when there are no radio suffering from being missing in the radio
+             group, all radio should not suffer from being missing. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').required = false;
+                if (document.getElementById('i1').mozMatchesSelector(':invalid') ||
+                    document.getElementById('i2').mozMatchesSelector(':invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/input-radio-nogroup-required-invalid.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="if (document.getElementById('i').mozMatchesSelector(':invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' checked required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/input-radio-nogroup-required-valid.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="if (!document.getElementById('i').mozMatchesSelector(':invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/input-radio-required.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: if one radio in a group has the required attribute and no radio is
+             checked, all radio in the group should suffer from being missing. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="if (!document.getElementById('i1').mozMatchesSelector(':invalid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
--- a/layout/reftests/css-invalid/input/reftest.list
+++ b/layout/reftests/css-invalid/input/reftest.list
@@ -18,9 +18,15 @@
 == input-url-valid.html input-url-ref.html
 == input-pattern-valid.html input-withtext-ref.html
 == input-pattern-invalid.html input-withtext-ref.html
 == input-type-barred.html input-button-ref.html
 == input-type-invalid.html input-ref.html
 == input-disabled-fieldset-1.html input-fieldset-ref.html
 == input-disabled-fieldset-2.html input-fieldset-ref.html
 == input-fieldset-legend.html input-fieldset-legend-ref.html
+== input-radio-required.html success-ref.html
+== input-radio-customerror.html success-ref.html
+== input-radio-dyn-valid-1.html success-ref.html
+== input-radio-dyn-valid-2.html success-ref.html
+== input-radio-nogroup-required-valid.html success-ref.html
+== input-radio-nogroup-required-invalid.html success-ref.html
 # input type='hidden' shouldn't show
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/input/success-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    SUCCESS
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-invalid/input/input-radio-customerror.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: if one radio in a group is suffering from a custom error, the other
+             radio should not be invalid. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = false;
+                document.getElementById('i1').setCustomValidity('foo');
+                if (!document.getElementById('i1').mozMatchesSelector(':-moz-ui-invalid') ||
+                document.getElementById('i2').mozMatchesSelector(':-moz-ui-invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio'>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-invalid/input/input-radio-dyn-valid-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: when there are no radio suffering from being missing in the radio
+             group, all radio should not suffer from being missing. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = true;
+                if (document.getElementById('i1').mozMatchesSelector(':-moz-ui-invalid') ||
+                    document.getElementById('i2').mozMatchesSelector(':-moz-ui-invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-invalid/input/input-radio-dyn-valid-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: when there are no radio suffering from being missing in the radio
+             group, all radio should not suffer from being missing. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = true;
+                document.getElementById('i1').required = false;
+                if (document.getElementById('i1').mozMatchesSelector(':-moz-ui-invalid') ||
+                    document.getElementById('i2').mozMatchesSelector(':-moz-ui-invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-invalid/input/input-radio-nogroup-required-invalid.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i').checked = true;
+                if (document.getElementById('i').mozMatchesSelector(':-moz-ui-invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-invalid/input/input-radio-nogroup-required-valid.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i').checked = false;
+                if (!document.getElementById('i').mozMatchesSelector(':-moz-ui-invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' required>
+  </body>
+</html>
deleted file mode 100644
--- a/layout/reftests/css-ui-invalid/input/input-radio-required-invalid-changed-2.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-  <!-- Test: if input isn't valid nor barred from constraint validation,
-             and its checkedness has changed,
-             it should be affected by :-moz-ui-invalid pseudo-class. -->
-  <link rel='stylesheet' type='text/css' href='style.css'>
-  <body onload="document.getElementById('i2').checked = false;
-                if (document.getElementById('i1').mozMatchesSelector(':-moz-ui-invalid') ||
-                    !document.getElementById('i2').mozMatchesSelector(':-moz-ui-invalid')) {
-                  document.body.textContent='FAIL';
-                } else {
-                  document.body.textContent='SUCCESS';
-                }
-                document.documentElement.className='';">
-    <input id='i1' type='radio'>
-    <input id='i2' type='radio' required>
-  </body>
-</html>
deleted file mode 100644
--- a/layout/reftests/css-ui-invalid/input/input-radio-required-invalid-default-2.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-  <!-- Test: if input isn't valid nor barred from constraint validation,
-             and its checkedness hasn't changed,
-             it should not be affected by :-moz-ui-invalid pseudo-class. -->
-  <link rel='stylesheet' type='text/css' href='style.css'>
-  <body onload="if (document.getElementById('i1').mozMatchesSelector(':-moz-ui-invalid') ||
-                    document.getElementById('i2').mozMatchesSelector(':-moz-ui-invalid')) {
-                  document.body.textContent='FAIL';
-                } else {
-                  document.body.textContent='SUCCESS';
-                }
-                document.documentElement.className='';">
-    <input id='i1' type='radio'>
-    <input id='i2' type='radio' required>
-  </body>
-</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-invalid/input/input-radio-required.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: if one radio in a group has the required attribute and no radio is
+             checked, all radio in the group should have :-moz-ui-invalid
+             pseudo-class. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = false;
+                if (!document.getElementById('i1').mozMatchesSelector(':-moz-ui-invalid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':-moz-ui-invalid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
--- a/layout/reftests/css-ui-invalid/input/reftest.list
+++ b/layout/reftests/css-ui-invalid/input/reftest.list
@@ -28,13 +28,17 @@
 == input-type-invalid.html input-ref.html
 == input-disabled-fieldset-1.html input-fieldset-ref.html
 == input-disabled-fieldset-2.html input-fieldset-ref.html
 == input-fieldset-legend.html input-fieldset-legend-ref.html
 == input-checkbox-required-invalid-changed.html success-ref.html
 == input-checkbox-required-invalid-default.html success-ref.html
 == input-radio-required-invalid-changed.html success-ref.html
 == input-radio-required-invalid-default.html success-ref.html
-== input-radio-required-invalid-changed-2.html success-ref.html
-== input-radio-required-invalid-default-2.html success-ref.html
 == input-file-required-invalid-changed.html input-file-ref.html
 == input-file-required-invalid-default.html input-file-ref.html
+== input-radio-required.html success-ref.html
+== input-radio-customerror.html success-ref.html
+== input-radio-dyn-valid-1.html success-ref.html
+== input-radio-dyn-valid-2.html success-ref.html
+== input-radio-nogroup-required-valid.html success-ref.html
+== input-radio-nogroup-required-invalid.html success-ref.html
 # input type='hidden' shouldn't show
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-valid/input/input-radio-customerror.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = false;
+                document.getElementById('i1').setCustomValidity('foo');
+                if (document.getElementById('i1').mozMatchesSelector(':-moz-ui-valid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':-moz-ui-valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio'>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-valid/input/input-radio-dyn-valid-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = true;
+                if (!document.getElementById('i1').mozMatchesSelector(':-moz-ui-valid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':-moz-ui-valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-valid/input/input-radio-dyn-valid-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = true;
+                document.getElementById('i1').required = false;
+                if (!document.getElementById('i1').mozMatchesSelector(':-moz-ui-valid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':-moz-ui-valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-valid/input/input-radio-nogroup-required-invalid.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i').checked = true;
+                if (!document.getElementById('i').mozMatchesSelector(':-moz-ui-valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-valid/input/input-radio-nogroup-required-valid.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i').checked = false;
+                if (document.getElementById('i').mozMatchesSelector(':-moz-ui-valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-ui-valid/input/input-radio-required.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = false;
+                if (document.getElementById('i1').mozMatchesSelector(':-moz-ui-valid') ||
+                    document.getElementById('i2').mozMatchesSelector(':-moz-ui-valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
--- a/layout/reftests/css-ui-valid/input/reftest.list
+++ b/layout/reftests/css-ui-valid/input/reftest.list
@@ -27,13 +27,17 @@
 == input-pattern-invalid.html input-withtext-ref.html
 == input-type-barred.html input-button-ref.html
 == input-type-invalid.html input-ref.html
 == input-disabled-fieldset-1.html input-fieldset-ref.html
 == input-disabled-fieldset-2.html input-fieldset-ref.html
 == input-fieldset-legend.html input-fieldset-legend-ref.html
 == input-checkbox-valid-changed.html success-ref.html
 == input-checkbox-valid-default.html success-ref.html
-== input-radio-valid-changed.html success-ref.html
-== input-radio-valid-default.html success-ref.html
 == input-file-valid-changed.html input-file-ref.html
 == input-file-valid-default.html input-file-ref.html
+== input-radio-required.html success-ref.html
+== input-radio-customerror.html success-ref.html
+== input-radio-dyn-valid-1.html success-ref.html
+== input-radio-dyn-valid-2.html success-ref.html
+== input-radio-nogroup-required-valid.html success-ref.html
+== input-radio-nogroup-required-invalid.html success-ref.html
 # input type='hidden' shouldn't show
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/input-radio-customerror.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').setCustomValidity('foo');
+                if (document.getElementById('i1').mozMatchesSelector(':valid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio'>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/input-radio-dyn-valid-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').checked = true;
+                if (!document.getElementById('i1').mozMatchesSelector(':valid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/input-radio-dyn-valid-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('i1').required = false;
+                if (!document.getElementById('i1').mozMatchesSelector(':valid') ||
+                    !document.getElementById('i2').mozMatchesSelector(':valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/input-radio-nogroup-required-invalid.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="if (!document.getElementById('i').mozMatchesSelector(':valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' checked required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/input-radio-nogroup-required-valid.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="if (document.getElementById('i').mozMatchesSelector(':valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i' type='radio' required>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/input-radio-required.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="if (document.getElementById('i1').mozMatchesSelector(':valid') ||
+                    document.getElementById('i2').mozMatchesSelector(':valid')) {
+                  document.body.textContent='FAIL';
+                } else {
+                  document.body.textContent='SUCCESS';
+                }
+                document.documentElement.className='';">
+    <input id='i1' name='foo' type='radio' required>
+    <input id='i2' name='foo' type='radio'>
+  </body>
+</html>
--- a/layout/reftests/css-valid/input/reftest.list
+++ b/layout/reftests/css-valid/input/reftest.list
@@ -18,9 +18,15 @@
 == input-url-valid.html input-url-ref.html
 == input-pattern-valid.html input-withtext-ref.html
 == input-pattern-invalid.html input-withtext-ref.html
 == input-type-barred.html input-button-ref.html
 == input-type-invalid.html input-ref.html
 == input-disabled-fieldset-1.html input-fieldset-ref.html
 == input-disabled-fieldset-2.html input-fieldset-ref.html
 == input-fieldset-legend.html input-fieldset-legend-ref.html
+== input-radio-required.html success-ref.html
+== input-radio-customerror.html success-ref.html
+== input-radio-dyn-valid-1.html success-ref.html
+== input-radio-dyn-valid-2.html success-ref.html
+== input-radio-nogroup-required-valid.html success-ref.html
+== input-radio-nogroup-required-invalid.html success-ref.html
 # input type='hidden' shouldn't show
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/input/success-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    SUCCESS
+  </body>
+</html>