Bug 604673 - <output> should be subject for constraint validation. r=smaug ui-r=limi a=bsmedberg
authorMounir Lamouri <mounir.lamouri@gmail.com>
Wed, 17 Nov 2010 17:30:03 +0100
changeset 57659 2eb1331a2bc42c081fde0511796195244c85e724
parent 57658 1b4d00d79ae61cca9f4aa94c35a36356d325a924
child 57660 6cfd43067a07e1e7d818c02b6a7b1ea62e8ace02
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewerssmaug, limi, bsmedberg
bugs604673
milestone2.0b8pre
Bug 604673 - <output> should be subject for constraint validation. r=smaug ui-r=limi a=bsmedberg
content/html/content/src/nsHTMLOutputElement.cpp
content/html/content/test/test_bug345624-1.html
layout/reftests/css-invalid/output/output-invalid.html
layout/reftests/css-invalid/output/output-ref.html
layout/reftests/css-invalid/output/output-valid.html
layout/reftests/css-invalid/output/reftest.list
layout/reftests/css-invalid/output/style.css
layout/reftests/css-invalid/reftest.list
layout/reftests/css-valid/output/output-invalid.html
layout/reftests/css-valid/output/output-ref.html
layout/reftests/css-valid/output/output-valid.html
layout/reftests/css-valid/output/reftest.list
layout/reftests/css-valid/output/style.css
layout/reftests/css-valid/reftest.list
layout/style/forms.css
--- a/content/html/content/src/nsHTMLOutputElement.cpp
+++ b/content/html/content/src/nsHTMLOutputElement.cpp
@@ -36,16 +36,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIDOMHTMLOutputElement.h"
 #include "nsGenericHTMLElement.h"
 #include "nsFormSubmission.h"
 #include "nsDOMSettableTokenList.h"
 #include "nsStubMutationObserver.h"
 #include "nsIConstraintValidation.h"
+#include "nsIEventStateManager.h"
+#include "mozAutoDocUpdate.h"
 
 
 class nsHTMLOutputElement : public nsGenericHTMLFormElement,
                             public nsIDOMHTMLOutputElement,
                             public nsStubMutationObserver,
                             public nsIConstraintValidation
 {
 public:
@@ -76,16 +78,18 @@ public:
 
   virtual bool IsDisabled() const { return PR_FALSE; }
 
   nsresult Clone(nsINodeInfo* aNodeInfo, nsINode** aResult) const;
 
   PRBool ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
                         const nsAString& aValue, nsAttrValue& aResult);
 
+  nsEventStates IntrinsicState() const;
+
   // This function is called when a callback function from nsIMutationObserver
   // has to be used to update the defaultValue attribute.
   void DescendantsChanged();
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
@@ -110,18 +114,16 @@ 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();
   }
 }
@@ -142,17 +144,32 @@ NS_INTERFACE_TABLE_HEAD(nsHTMLOutputElem
 NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLOutputElement)
 
 NS_IMPL_ELEMENT_CLONE(nsHTMLOutputElement)
 
 
 NS_IMPL_STRING_ATTR(nsHTMLOutputElement, Name, name)
 
 // nsIConstraintValidation
-NS_IMPL_NSICONSTRAINTVALIDATION(nsHTMLOutputElement)
+NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(nsHTMLOutputElement)
+
+NS_IMETHODIMP
+nsHTMLOutputElement::SetCustomValidity(const nsAString& aError)
+{
+  nsIConstraintValidation::SetCustomValidity(aError);
+
+  nsIDocument* doc = GetCurrentDoc();
+  if (doc) {
+    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;
+}
 
 NS_IMETHODIMP
 nsHTMLOutputElement::Reset()
 {
   mValueModeFlag = eModeDefault;
   nsresult rv = nsContentUtils::SetNodeTextContent(this, mDefaultValue,
                                                    PR_TRUE);
   return rv;
@@ -175,16 +192,28 @@ nsHTMLOutputElement::ParseAttribute(PRIn
       return PR_TRUE;
     }
   }
 
   return nsGenericHTMLFormElement::ParseAttribute(aNamespaceID, aAttribute,
                                                   aValue, aResult);
 }
 
+nsEventStates
+nsHTMLOutputElement::IntrinsicState() const
+{
+  nsEventStates states = nsGenericHTMLFormElement::IntrinsicState();
+
+  // We don't have to call IsCandidateForConstraintValidation()
+  // because <output> can't be barred from constraint validation.
+  states |= IsValid() ? NS_EVENT_STATE_VALID : NS_EVENT_STATE_INVALID;
+
+  return states;
+}
+
 NS_IMETHODIMP
 nsHTMLOutputElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElement::GetForm(aForm);
 }
 
 NS_IMETHODIMP
 nsHTMLOutputElement::GetType(nsAString& aType)
--- a/content/html/content/test/test_bug345624-1.html
+++ b/content/html/content/test/test_bug345624-1.html
@@ -18,18 +18,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=345624">Mozilla Bug 345624</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   <fieldset id='f'></fieldset>
   <input id='i' oninvalid="invalidEventHandler(event);">
   <button id='b' oninvalid="invalidEventHandler(event);"></button>
   <select id='s' oninvalid="invalidEventHandler(event);"></select>
   <textarea id='t' oninvalid="invalidEventHandler(event);"></textarea>
+  <output id='o' oninvalid="invalidEventHandler(event);"></output>
   <keygen id='k'></keygen>
-  <output id='o'></output>
   <object id='obj'></object>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 345624 **/
 
 var gInvalid = false;
@@ -89,18 +89,18 @@ function checkConstraintValidationAPIDef
 
 function checkDefaultPseudoClass()
 {
   is(window.getComputedStyle(document.getElementById('f'), null)
        .getPropertyValue('background-color'), "rgb(0, 0, 0)",
      "Nor :valid and :invalid should apply");
 
   is(window.getComputedStyle(document.getElementById('o'), null)
-       .getPropertyValue('background-color'), "rgb(0, 0, 0)",
-     "Nor :valid and :invalid should apply");
+       .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+     ":valid should apply");
 
   is(window.getComputedStyle(document.getElementById('obj'), null)
        .getPropertyValue('background-color'), "rgb(0, 0, 0)",
      "Nor :valid and :invalid should apply");
 
   todo_is(window.getComputedStyle(document.getElementById('k'), null)
        .getPropertyValue('background-color'), "rgb(0, 0, 0)",
      "Nor :valid and :invalid should apply");
@@ -121,19 +121,19 @@ function checkDefaultPseudoClass()
        .getPropertyValue('background-color'), "rgb(0, 255, 0)",
      ":valid pseudo-class should apply");
 }
 
 function checkSpecificWillValidate()
 {
   // fieldset, output, object, keygen (TODO) and select elements
   ok(!document.getElementById('f').willValidate, "Fielset element should be barred from constraint validation");
-  ok(!document.getElementById('o').willValidate, "Output element should be barred from constraint validation");
   ok(!document.getElementById('obj').willValidate, "Object element should be barred from constraint validation");
   todo(!document.getElementById('k').willValidate, "Keygen element should be barred from constraint validation");
+  ok(document.getElementById('o').willValidate, "Output element should not be barred from constraint validation");
   ok(document.getElementById('s').willValidate, "Select element should not be barred from constraint validation");
 
   // input element
   i = document.getElementById('i');
   i.type = "hidden";
   ok(!i.willValidate, "Hidden state input should be barred from constraint validation");
   is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
      "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
@@ -184,23 +184,26 @@ function checkSpecificWillValidate()
   // TODO: PROGRESS
   // TODO: METER
 }
 
 function checkCommonWillValidate(element)
 {
   // Not checking the default value because it has been checked previously.
 
-  element.disabled = true;
-  ok(!element.willValidate, "Disabled element should be barred from constraint validation");
+  // Not checking output elements because they can't be disabled.
+  if (element.tagName != 'OUTPUT') {
+    element.disabled = true;
+    ok(!element.willValidate, "Disabled element should be barred from constraint validation");
 
-  is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
-     "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+    is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+       "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
 
-  element.removeAttribute('disabled');
+    element.removeAttribute('disabled');
+  }
 
   // TODO: If an element has a datalist element ancestor, it is barred from constraint validation.
 }
 
 function checkCustomError(element, isBarred)
 {
   element.setCustomValidity("message");
   if (!isBarred) {
@@ -288,38 +291,40 @@ checkConstraintValidationAPIDefaultValue
 checkConstraintValidationAPIDefaultValues(document.getElementById('k'));
 checkConstraintValidationAPIDefaultValues(document.getElementById('o'));
 checkConstraintValidationAPIDefaultValues(document.getElementById('obj'));
 
 checkDefaultPseudoClass();
 
 checkSpecificWillValidate();
 
-// Not checking fieldset, output, object and keygen
+// Not checking fieldset, object and keygen
 // because they are always barred from constraint validation.
 checkCommonWillValidate(document.getElementById('i'));
 checkCommonWillValidate(document.getElementById('b'));
 checkCommonWillValidate(document.getElementById('s'));
 checkCommonWillValidate(document.getElementById('t'));
+checkCommonWillValidate(document.getElementById('o'));
 
 /* TODO: add "keygen" element */
 checkCustomError(document.getElementById('i'), false);
 checkCustomError(document.getElementById('b'), false);
 checkCustomError(document.getElementById('s'), false);
 checkCustomError(document.getElementById('t'), false);
+checkCustomError(document.getElementById('o'), false);
 checkCustomError(document.getElementById('f'), true);
-checkCustomError(document.getElementById('o'), true);
 checkCustomError(document.getElementById('obj'), true);
 
-// Not checking fieldset, output, object and keygen
+// Not checking fieldset, object and keygen
 // because they are always barred from constraint validation.
 checkCheckValidity(document.getElementById('i'));
 checkCheckValidity(document.getElementById('b'));
 checkCheckValidity(document.getElementById('s'));
 checkCheckValidity(document.getElementById('t'));
+checkCheckValidity(document.getElementById('o'));
 
 /* TODO: add "keygen" element */
 checkValidityStateObjectAliveWithoutElement("fieldset");
 checkValidityStateObjectAliveWithoutElement("input");
 checkValidityStateObjectAliveWithoutElement("button");
 checkValidityStateObjectAliveWithoutElement("select");
 checkValidityStateObjectAliveWithoutElement("textarea");
 checkValidityStateObjectAliveWithoutElement("output");
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/output/output-invalid.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: if output has a custom error, it should not be affected by :valid
+             pseudo-class. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('b').setCustomValidity('foo'); document.documentElement.className='';">
+    <output class='invalid' id='b'>foo</output>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/output/output-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body>
+    <output class='ref'>foo</output>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/output/output-valid.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <!-- Test: if output has no custom error and is not barred from constraint
+             validation, it should be affected by :valid pseudo-class. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body>
+    <output class='notinvalid'>foo</output>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/output/reftest.list
@@ -0,0 +1,2 @@
+== output-valid.html output-ref.html
+== output-invalid.html output-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-invalid/output/style.css
@@ -0,0 +1,22 @@
+/* Override default style */
+output {
+  color: black;
+}
+
+output.notinvalid {
+  color: green;
+}
+output.notinvalid:invalid {
+  color: red;
+}
+
+output.invalid {
+  color: red;
+}
+output.invalid:invalid {
+  color: green;
+}
+
+output.ref {
+  color: green;
+}
--- a/layout/reftests/css-invalid/reftest.list
+++ b/layout/reftests/css-invalid/reftest.list
@@ -1,11 +1,12 @@
 # :invalid should not apply on fieldset (always barred from constraint validation)
 include fieldset/reftest.list
 
 # :invalid should apply on the following elements
 include button/reftest.list
 include input/reftest.list
 include select/reftest.list
 include textarea/reftest.list
+include output/reftest.list
 
 # default :invalid style
 include default-style/reftest.list
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/output/output-invalid.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: if output has a custom error, it should not be affected by :valid
+             pseudo-class. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body onload="document.getElementById('b').setCustomValidity('foo'); document.documentElement.className='';">
+    <output class='notvalid' id='b'>foo</output>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/output/output-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body>
+    <output class='ref'>foo</output>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/output/output-valid.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <!-- Test: if output has no custom error and is not barred from constraint
+             validation, it should be affected by :valid pseudo-class. -->
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <body>
+    <output class='valid'>foo</output>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/output/reftest.list
@@ -0,0 +1,2 @@
+== output-valid.html output-ref.html
+== output-invalid.html output-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-valid/output/style.css
@@ -0,0 +1,22 @@
+/* Override default style */
+output {
+  color: black;
+}
+
+output.notvalid {
+  color: green;
+}
+output.notvalid:valid {
+  color: red;
+}
+
+output.valid {
+  color: red;
+}
+output.valid:valid {
+  color: green;
+}
+
+output.ref {
+  color: green;
+}
--- a/layout/reftests/css-valid/reftest.list
+++ b/layout/reftests/css-valid/reftest.list
@@ -1,8 +1,11 @@
 # :valid should not apply on fieldset (always barred from constraint validation)
 include fieldset/reftest.list
 
 # :valid should apply on the following elements
 include button/reftest.list
 include input/reftest.list
 include select/reftest.list
 include textarea/reftest.list
+include output/reftest.list
+include output/reftest.list
+include output/reftest.list
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -619,24 +619,25 @@ optgroup:before {
   */
 input[type="file"] > input[type="text"] {
   direction: ltr !important;
   text-align: inherit;
 }
 
 /**
  * Set default style for invalid elements.
- * <output> is the only element which may not follow this rule (but color: red).
- * However it is barred from constraint validation per the specifications so, at
- * least for now, it will not be affected by :invalid.
  */
-:invalid {
+:not(output):invalid {
   box-shadow: 0 0 1.5px 1px red;
 }
 
+output:invalid {
+  color: red;
+}
+
 @media print {
   input, textarea, select, button {
     -moz-user-input: none !important;
   }
 
   input[type="file"] { height: 2em; }
 }