Bug 612730 (3/3) - :-moz-ui-invalid should not apply if the element was valid on focus and :-moz-ui-valid should not apply if no style was applying on focus. r=bz a=jst
authorMounir Lamouri <mounir.lamouri@gmail.com>
Wed, 24 Nov 2010 11:09:31 +0100
changeset 58158 f4a67c552e73c72659d76baaac730e8ae071d856
parent 58157 8fd7b019a2e6e78a53f17ac2143a8707c8fef4a2
child 58159 af2c1d05ce6e9d1965775baad19ccdc790778a6d
push id17175
push usermlamouri@mozilla.com
push dateWed, 24 Nov 2010 10:15:50 +0000
treeherderautoland@7f5cd850578e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, jst
bugs612730
milestone2.0b8pre
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 612730 (3/3) - :-moz-ui-invalid should not apply if the element was valid on focus and :-moz-ui-valid should not apply if no style was applying on focus. r=bz a=jst
content/html/content/src/nsHTMLSelectElement.cpp
content/html/content/src/nsHTMLSelectElement.h
content/html/content/test/Makefile.in
content/html/content/test/test_bug605124-2.html
content/html/content/test/test_bug605125-2.html
content/html/content/test/test_bug612730.html
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -67,17 +67,16 @@
 #include "nsIFrame.h"
 
 #include "nsDOMError.h"
 #include "nsServiceManagerUtils.h"
 #include "nsRuleData.h"
 #include "nsEventDispatcher.h"
 #include "mozilla/dom/Element.h"
 #include "mozAutoDocUpdate.h"
-#include "nsHTMLFormElement.h"
 
 using namespace mozilla::dom;
 
 NS_IMPL_ISUPPORTS1(nsSelectState, nsSelectState)
 NS_DEFINE_STATIC_IID_ACCESSOR(nsSelectState, NS_SELECT_STATE_IID)
 
 //----------------------------------------------------------------------
 //
@@ -146,16 +145,18 @@ nsHTMLSelectElement::nsHTMLSelectElement
   : nsGenericHTMLFormElement(aNodeInfo),
     mOptions(new nsHTMLOptionCollection(this)),
     mIsDoneAddingChildren(!aFromParser),
     mDisabledChanged(PR_FALSE),
     mMutating(PR_FALSE),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mSelectionHasChanged(PR_FALSE),
     mDefaultSelectionSet(PR_FALSE),
+    mCanShowInvalidUI(PR_TRUE),
+    mCanShowValidUI(PR_TRUE),
     mNonOptionChildren(0),
     mOptGroupCount(0),
     mSelectedIndex(-1)
 {
   // FIXME: Bug 328908, set mOptions in an Init function and get rid of null
   // checks.
 
   // DoneAddingChildren() will be called later if it's from the parser,
@@ -1560,37 +1561,73 @@ nsHTMLSelectElement::PreHandleEvent(nsEv
         uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED) {
       return NS_OK;
     }
   }
 
   return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
 }
 
+nsresult
+nsHTMLSelectElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
+{
+  if (aVisitor.mEvent->message == NS_FOCUS_CONTENT) {
+    // If the invalid UI is shown, we should show it while focused and
+    // update the invalid/valid UI.
+    mCanShowInvalidUI = !IsValid() && ShouldShowInvalidUI();
+
+    // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
+    // UI while focused.
+    mCanShowValidUI = ShouldShowValidUI();
+
+    // We don't have to update NS_EVENT_STATE_MOZ_UI_INVALID nor
+    // NS_EVENT_STATE_MOZ_UI_VALID given that the states should not change.
+  } else if (aVisitor.mEvent->message == NS_BLUR_CONTENT) {
+    mCanShowInvalidUI = PR_TRUE;
+    mCanShowValidUI = PR_TRUE;
+
+    nsIDocument* doc = GetCurrentDoc();
+    if (doc) {
+      MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
+      doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_MOZ_UI_VALID |
+                                              NS_EVENT_STATE_MOZ_UI_INVALID);
+    }
+  }
+
+  return nsGenericHTMLFormElement::PostHandleEvent(aVisitor);
+}
+
 nsEventStates
 nsHTMLSelectElement::IntrinsicState() const
 {
   nsEventStates state = nsGenericHTMLFormElement::IntrinsicState();
 
   if (IsCandidateForConstraintValidation()) {
     if (IsValid()) {
       state |= NS_EVENT_STATE_VALID;
-
-      if ((mForm && mForm->HasEverTriedInvalidSubmit()) ||
-          mSelectionHasChanged) {
-        state |= NS_EVENT_STATE_MOZ_UI_VALID;
-      }
     } else {
       state |= NS_EVENT_STATE_INVALID;
 
-      if ((mForm && mForm->HasEverTriedInvalidSubmit()) ||
-          mSelectionHasChanged || GetValidityState(VALIDITY_STATE_CUSTOM_ERROR)) {
+      if (mCanShowInvalidUI && ShouldShowInvalidUI()) {
         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
       }
     }
+
+    // :-moz-ui-valid applies if all the following are true:
+    // 1. The element is not focused, or had either :-moz-ui-valid or
+    //    :-moz-ui-invalid applying before it was focused ;
+    // 2. The element is either valid or isn't allowed to have
+    //    :-moz-ui-invalid applying ;
+    // 3. The rules to have :-moz-ui-valid applying are fulfilled
+    //    (see ShouldShowValidUI()).
+    if (mCanShowValidUI &&
+        (IsValid() || !mCanShowInvalidUI) &&
+        ShouldShowValidUI()) {
+      state |= NS_EVENT_STATE_MOZ_UI_VALID;
+    }
   }
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
     state |= NS_EVENT_STATE_REQUIRED;
   } else {
     state |= NS_EVENT_STATE_OPTIONAL;
   }
 
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -56,16 +56,17 @@
 
 // PresState
 #include "nsXPCOM.h"
 #include "nsPresState.h"
 #include "nsIComponentManager.h"
 #include "nsCheapSets.h"
 #include "nsLayoutErrors.h"
 #include "nsHTMLOptionElement.h"
+#include "nsHTMLFormElement.h"
 
 class nsHTMLSelectElement;
 
 /**
  * The collection of options in the select (what you get back when you do
  * select.options in DOM)
  */
 class nsHTMLOptionCollection: public nsIDOMHTMLOptionsCollection,
@@ -262,16 +263,17 @@ public:
   // nsIDOMHTMLSelectElement
   NS_DECL_NSIDOMHTMLSELECTELEMENT
 
   // nsIDOMHTMLSelectElement_Mozilla_2_0_Branch
   NS_DECL_NSIDOMHTMLSELECTELEMENT_MOZILLA_2_0_BRANCH
 
   // nsIContent
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
+  virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
 
   virtual PRBool IsHTMLFocusable(PRBool aWithMouse, PRBool *aIsFocusable, PRInt32 *aTabIndex);
   virtual nsresult InsertChildAt(nsIContent* aKid, PRUint32 aIndex,
                                  PRBool aNotify);
   virtual nsresult RemoveChildAt(PRUint32 aIndex, PRBool aNotify, PRBool aMutationEvent = PR_TRUE);
 
   // Overriden nsIFormControl methods
   NS_IMETHOD_(PRUint32) GetType() const { return NS_FORM_SELECT; }
@@ -508,16 +510,49 @@ protected:
   {
     return PR_TRUE;
   }
 
   nsresult SetSelectedIndexInternal(PRInt32 aIndex, PRBool aNotify);
 
   void SetSelectionChanged(PRBool aValue, PRBool aNotify);
 
+  /**
+   * Return whether an invalid element should have a specific UI for being invalid
+   * (with :-moz-ui-invalid pseudo-class).
+   *
+   * @return Whether the invalid element should have a UI for being invalid.
+   * @note The caller has to be sure the element is invalid before calling.
+   */
+  bool ShouldShowInvalidUI() const {
+    NS_ASSERTION(!IsValid(), "You should not call ShouldShowInvalidUI if the "
+                             "element is valid!");
+
+    /**
+     * Always show the invalid UI if:
+     * - the form has already tried to be submitted but was invalid;
+     * - the element is suffering from a custom error;
+     *
+     * Otherwise, show the invalid UI if the selection has been changed.
+     */
+    return mSelectionHasChanged ||
+           (mForm && mForm->HasEverTriedInvalidSubmit()) ||
+           GetValidityState(VALIDITY_STATE_CUSTOM_ERROR);
+  }
+
+  /**
+   * Return whether an element should show the valid UI.
+   *
+   * @return Whether the valid UI should be shown.
+   * @note This doesn't take into account the validity of the element.
+   */
+  bool ShouldShowValidUI() const {
+    return mSelectionHasChanged ||
+           (mForm && mForm->HasEverTriedInvalidSubmit());
+  }
   /** The options[] array */
   nsRefPtr<nsHTMLOptionCollection> mOptions;
   /** false if the parser is in the middle of adding children. */
   PRPackedBool    mIsDoneAddingChildren;
   /** true if our disabled state has changed from the default **/
   PRPackedBool    mDisabledChanged;
   /** true if child nodes are being added or removed.
    *  Used by nsSafeOptionListMutation.
@@ -530,16 +565,24 @@ protected:
   /**
    * True if the selection has changed since the element's creation.
    */
   PRPackedBool    mSelectionHasChanged;
   /**
    * True if the default selected option has been set.
    */
   PRPackedBool    mDefaultSelectionSet;
+  /**
+   * True if :-moz-ui-invalid can be shown.
+   */
+  PRPackedBool    mCanShowInvalidUI;
+  /**
+   * True if :-moz-ui-valid can be shown.
+   */
+  PRPackedBool    mCanShowValidUI;
 
   /** The number of non-options as children of the select */
   PRUint32  mNonOptionChildren;
   /** The number of optgroups anywhere under the select */
   PRUint32  mOptGroupCount;
   /**
    * The current selected index for selectedIndex (will be the first selected
    * index if multiple are selected)
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -242,12 +242,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug601061.html \
 		test_bug596511.html \
 		reflect.js \
 		test_bug613113.html \
 		test_bug605124-1.html \
 		test_bug605124-2.html \
 		test_bug605125-1.html \
 		test_bug605125-2.html \
+		test_bug612730.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/content/html/content/test/test_bug605124-2.html
+++ b/content/html/content/test/test_bug605124-2.html
@@ -11,16 +11,24 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=605124">Mozilla Bug 605124</a>
 <p id="display"></p>
 <div id="content">
   <input required>
   <textarea required></textarea>
+  <select required>
+    <option value="">foo</option>
+    <option>bar</option>
+  </select>
+  <select multiple required>
+    <option value="">foo</option>
+    <option>bar</option>
+  </select>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 605124 **/
 
 function checkPseudoClass(aElement, aExpected)
 {
@@ -61,15 +69,45 @@ function checkElement(aElement)
   checkPseudoClass(aElement, true);
   // with .value
   aElement.value = 'f';
   checkPseudoClass(aElement, false);
   aElement.value = '';
   checkPseudoClass(aElement, true);
 }
 
+function checkSelectElement(aElement)
+{
+  checkPseudoClass(aElement, false);
+
+  // Focusing while :-moz-ui-invalid doesn't apply,
+  // the pseudo-class should not apply while changing selection.
+  aElement.focus();
+  checkPseudoClass(aElement, false);
+
+  aElement.selectedIndex = 1;
+  checkPseudoClass(aElement, false);
+  aElement.selectedIndex = 0;
+  checkPseudoClass(aElement, false);
+
+  aElement.blur();
+  checkPseudoClass(aElement, true);
+
+  // Focusing while :-moz-ui-invalid applies,
+  // the pseudo-class should apply while changing selection if appropriate.
+  aElement.focus();
+  checkPseudoClass(aElement, true);
+
+  aElement.selectedIndex = 1;
+  checkPseudoClass(aElement, false);
+  aElement.selectedIndex = 0;
+  checkPseudoClass(aElement, true);
+}
+
 checkElement(document.getElementsByTagName('input')[0]);
 checkElement(document.getElementsByTagName('textarea')[0]);
+checkSelectElement(document.getElementsByTagName('select')[0]);
+checkSelectElement(document.getElementsByTagName('select')[1]);
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/html/content/test/test_bug605125-2.html
+++ b/content/html/content/test/test_bug605125-2.html
@@ -11,16 +11,24 @@ https://bugzilla.mozilla.org/show_bug.cg
   <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=605125">Mozilla Bug 605125</a>
 <p id="display"></p>
 <div id="content">
   <input>
   <textarea></textarea>
+  <select>
+    <option value="">foo</option>
+    <option>bar</option>
+  </select>
+  <select multiple>
+    <option value="">foo</option>
+    <option>bar</option>
+  </select>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 605125 **/
 
 function checkPseudoClass(aElement, aExpected)
 {
@@ -80,15 +88,59 @@ function checkElement(aElement)
   checkPseudoClass(aElement, false);
   // with .value
   aElement.value = 'f';
   checkPseudoClass(aElement, true);
   aElement.value = '';
   checkPseudoClass(aElement, false);
 }
 
+function checkSelectElement(aElement)
+{
+  checkPseudoClass(aElement, false);
+
+  // Focusing while :-moz-ui-valid doesn't apply,
+  // the pseudo-class should not apply while changing selection.
+  aElement.focus();
+  checkPseudoClass(aElement, false);
+
+  aElement.selectedIndex = 1;
+  checkPseudoClass(aElement, false);
+  aElement.selectedIndex = 0;
+  checkPseudoClass(aElement, false);
+
+  aElement.blur();
+  checkPseudoClass(aElement, true);
+
+  // Focusing while :-moz-ui-valid applies,
+  // the pseudo-class should apply while changing selection if appropriate.
+  aElement.focus();
+  checkPseudoClass(aElement, true);
+
+  aElement.selectedIndex = 1;
+  checkPseudoClass(aElement, true);
+  aElement.selectedIndex = 0;
+  checkPseudoClass(aElement, true);
+
+  aElement.blur();
+  aElement.required = true;
+  checkPseudoClass(aElement, false);
+
+  // Focusing while :-moz-ui-invalid applies,
+  // the pseudo-class should apply while changing selection if appropriate.
+  aElement.focus();
+  checkPseudoClass(aElement, false);
+
+  aElement.selectedIndex = 1;
+  checkPseudoClass(aElement, true);
+  aElement.selectedIndex = 0;
+  checkPseudoClass(aElement, false);
+}
+
 checkElement(document.getElementsByTagName('input')[0]);
 checkElement(document.getElementsByTagName('textarea')[0]);
+checkSelectElement(document.getElementsByTagName('select')[0]);
+checkSelectElement(document.getElementsByTagName('select')[1]);
 
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug612730.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=612730
+-->
+<head>
+  <title>Test for Bug 612730</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.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=612730">Mozilla Bug 612730</a>
+<p id="display"></p>
+<div id="content">
+  <select multiple required>
+    <option value="">foo</option>
+    <option value="">bar</option>
+  </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 612730 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest()
+{
+  var select = document.getElementsByTagName('select')[0];
+
+  select.addEventListener("focus", function() {
+    select.removeEventListener("focus", arguments.callee, false);
+
+    isnot(select.selectedIndex, -1, "Something should have been selected");
+
+    ok(!select.mozMatchesSelector(":-moz-ui-valid"),
+       ":-moz-ui-valid should not apply");
+    ok(!select.mozMatchesSelector(":-moz-ui-invalid"),
+       ":-moz-ui-invalid should not apply");
+
+    SimpleTest.finish();
+  }, false);
+
+  synthesizeMouse(select, 5, 5, {});
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>