Bug 598833 part 12. Add dom::Element::UpdateState and use it in various places where elements update their own state. r=smaug,sdwilsh,mounir
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 31 May 2011 21:46:57 -0400
changeset 70411 5ad2fcf8d9ed
parent 70410 fa98ebd237d0
child 70412 c2360fe69660
push id20314
push usermlamouri@mozilla.com
push dateWed, 01 Jun 2011 08:12:29 +0000
treeherdermozilla-central@840644b2b6a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, sdwilsh, mounir
bugs598833
milestone7.0a1
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 598833 part 12. Add dom::Element::UpdateState and use it in various places where elements update their own state. r=smaug,sdwilsh,mounir
content/base/public/Element.h
content/base/public/nsIContent.h
content/base/src/Link.cpp
content/base/src/nsGenConImageContent.cpp
content/base/src/nsGenericDOMDataNode.cpp
content/base/src/nsGenericElement.cpp
content/base/src/nsGenericElement.h
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsObjectLoadingContent.h
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsGenericHTMLElement.h
content/html/content/src/nsHTMLButtonElement.cpp
content/html/content/src/nsHTMLFieldSetElement.cpp
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLImageElement.cpp
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/src/nsHTMLObjectElement.cpp
content/html/content/src/nsHTMLOptGroupElement.cpp
content/html/content/src/nsHTMLOptionElement.cpp
content/html/content/src/nsHTMLOutputElement.cpp
content/html/content/src/nsHTMLProgressElement.cpp
content/html/content/src/nsHTMLSelectElement.cpp
content/html/content/src/nsHTMLSelectElement.h
content/html/content/src/nsHTMLSharedObjectElement.cpp
content/html/content/src/nsHTMLTextAreaElement.cpp
content/html/content/src/nsRadioVisitor.cpp
content/html/content/test/test_bug345822.html
content/html/document/src/nsHTMLDocument.cpp
content/mathml/content/src/nsMathMLElement.cpp
content/svg/content/src/nsSVGFilters.cpp
content/svg/content/src/nsSVGImageElement.cpp
content/xtf/src/nsXTFElementWrapper.cpp
content/xul/content/src/nsXULElement.cpp
content/xul/content/src/nsXULElement.h
layout/generic/nsVideoFrame.cpp
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -80,78 +80,120 @@ enum {
 
   // Remaining bits are for subclasses
   ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 4
 };
 
 namespace mozilla {
 namespace dom {
 
+class Link;
+
 class Element : public nsIContent
 {
 public:
 #ifdef MOZILLA_INTERNAL_API
-  Element(already_AddRefed<nsINodeInfo> aNodeInfo) : nsIContent(aNodeInfo) {}
+  Element(already_AddRefed<nsINodeInfo> aNodeInfo) :
+    nsIContent(aNodeInfo),
+    mState(NS_EVENT_STATE_MOZ_READONLY)
+  {}
 #endif // MOZILLA_INTERNAL_API
 
   /**
    * Method to get the full state of this element.  See nsEventStates.h for
    * the possible bits that could be set here.
    */
   nsEventStates State() const {
-    return IntrinsicState() | mState;
+    // mState is maintained by having whoever might have changed it
+    // call UpdateState() or one of the other mState mutators.
+    return mState;
   }
 
   /**
    * Request an update of the link state for this element.  This will
    * make sure that if the element is a link at all then either
    * NS_EVENT_STATE_VISITED or NS_EVENT_STATE_UNVISITED is set in
    * mState, and a history lookup kicked off if needed to find out
    * whether the link is really visited.  This method will NOT send any
    * state change notifications.  If you want them to happen for this
    * call, you need to handle them yourself.
    */
   virtual void RequestLinkStateUpdate();
 
+  /**
+   * Ask this element to update its state.  If aNotify is false, then
+   * state change notifications will not be dispatched; in that
+   * situation it is the caller's responsibility to dispatch them.
+   *
+   * In general, aNotify should only be false if we're guaranteed that
+   * the element can't have a frame no matter what its style is
+   * (e.g. if we're in the middle of adding it to the document or
+   * removing it from the document).
+   */
+  void UpdateState(bool aNotify);
+
 protected:
   /**
    * Method to get the _intrinsic_ content state of this element.  This is the
    * state that is independent of the element's presentation.  To get the full
    * content state, use State().  See nsEventStates.h for
    * the possible bits that could be set here.
    */
   virtual nsEventStates IntrinsicState() const;
 
   /**
    * Method to update mState with link state information.  This does not notify.
    */
   void UpdateLinkState(nsEventStates aState);
 
+  /**
+   * Method to add state bits.  This should be called from subclass
+   * constructors to set up our event state correctly at construction
+   * time and other places where we don't want to notify a state
+   * change.
+   */
+  void AddStatesSilently(nsEventStates aStates) {
+    mState |= aStates;
+  }
+
+  /**
+   * Method to remove state bits.  This should be called from subclass
+   * constructors to set up our event state correctly at construction
+   * time and other places where we don't want to notify a state
+   * change.
+   */
+  void RemoveStatesSilently(nsEventStates aStates) {
+    mState &= ~aStates;
+  }
+
 private:
   // Need to allow the ESM, nsGlobalWindow, and the focus manager to
   // set our state
   friend class ::nsEventStateManager;
   friend class ::nsGlobalWindow;
   friend class ::nsFocusManager;
 
+  // Also need to allow Link to call UpdateLinkState.
+  friend class Link;
+
   void NotifyStateChange(nsEventStates aStates);
 
   // Methods for the ESM to manage state bits.  These will handle
   // setting up script blockers when they notify, so no need to do it
   // in the callers unless desired.
   void AddStates(nsEventStates aStates) {
     NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES),
                     "Should only be adding ESM-managed states here");
-    mState |= aStates;
+    AddStatesSilently(aStates);
     NotifyStateChange(aStates);
   }
   void RemoveStates(nsEventStates aStates) {
     NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES),
                     "Should only be removing ESM-managed states here");
-    mState &= ~aStates;
+    RemoveStatesSilently(aStates);
     NotifyStateChange(aStates);
   }
 
   nsEventStates mState;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -71,18 +71,18 @@ enum nsLinkState {
   eLinkState_Unknown    = 0,
   eLinkState_Unvisited  = 1,
   eLinkState_Visited    = 2,
   eLinkState_NotLink    = 3
 };
 
 // IID for the nsIContent interface
 #define NS_ICONTENT_IID       \
-{ 0xba1c9e22, 0x4b73, 0x42ae, \
- { 0xb6, 0x45, 0xa7, 0x83, 0xd0, 0x7e, 0xee, 0x2c } }
+{ 0x860ee35b, 0xe505, 0x438f, \
+ { 0xa7, 0x7b, 0x65, 0xb9, 0xf5, 0x0b, 0xe5, 0x29 } }
 
 /**
  * A node of content in a document's content model. This interface
  * is supported by all content objects.
  */
 class nsIContent : public nsINode {
 public:
 #ifdef MOZILLA_INTERNAL_API
@@ -851,19 +851,20 @@ public:
    * content node (if applicable).  Returns null if there is no
    * "class" attribute for this type of content node.
    */
   virtual nsIAtom *GetClassAttributeName() const = 0;
 
   /**
    * Should be called when the node can become editable or when it can stop
    * being editable (for example when its contentEditable attribute changes,
-   * when it is moved into an editable parent, ...).
+   * when it is moved into an editable parent, ...).  If aNotify is true and
+   * the node is an element, this will notify the state change.
    */
-  virtual void UpdateEditableState();
+  virtual void UpdateEditableState(PRBool aNotify);
 
   /**
    * Destroy this node and its children. Ideally this shouldn't be needed
    * but for now we need to do it to break cycles.
    */
   virtual void DestroyContent() = 0;
 
   /**
--- a/content/base/src/Link.cpp
+++ b/content/base/src/Link.cpp
@@ -80,35 +80,28 @@ Link::GetLinkState() const
 void
 Link::SetLinkState(nsLinkState aState)
 {
   NS_ASSERTION(mRegistered,
                "Setting the link state of an unregistered Link!");
   NS_ASSERTION(mLinkState != aState,
                "Setting state to the currently set state!");
 
-  // Remember our old link state for when we notify.
-  nsEventStates oldLinkState = LinkState();
-
   // Set our current state as appropriate.
   mLinkState = aState;
 
   // Per IHistory interface documentation, we are no longer registered.
   mRegistered = false;
 
-  // Notify the document that our visited state has changed.
-  Element *element = mElement;
-  nsIDocument *doc = element->GetCurrentDoc();
-  NS_ASSERTION(doc, "Registered but we have no document?!");
-  nsEventStates newLinkState = LinkState();
-  NS_ASSERTION(newLinkState == NS_EVENT_STATE_VISITED ||
-               newLinkState == NS_EVENT_STATE_UNVISITED,
-               "Unexpected state obtained from LinkState()!");
-  nsAutoScriptBlocker scriptBlocker;
-  doc->ContentStateChanged(element, oldLinkState ^ newLinkState);
+  NS_ABORT_IF_FALSE(LinkState() == NS_EVENT_STATE_VISITED ||
+                    LinkState() == NS_EVENT_STATE_UNVISITED,
+                    "Unexpected state obtained from LinkState()!");
+
+  // Tell the element to update its visited state
+  mElement->UpdateState(true);
 }
 
 nsEventStates
 Link::LinkState() const
 {
   // We are a constant method, but we are just lazily doing things and have to
   // track that state.  Cast away that constness!
   Link *self = const_cast<Link *>(this);
@@ -479,25 +472,26 @@ Link::ResetLinkState(bool aNotify)
   UnregisterFromHistory();
 
   // Update our state back to the default.
   mLinkState = defaultState;
 
   // Get rid of our cached URI.
   mCachedURI = nsnull;
 
-  // If aNotify is true, notify both of the visited-related states.  We have
-  // to do that, because we might be racing with a response from history and
-  // hence need to make sure that we get restyled whether we were visited or
-  // not before.  In particular, we need to make sure that our LinkState() is
-  // called so that we'll start a new history query as needed.
-  if (aNotify && doc) {
-    nsEventStates changedState = NS_EVENT_STATE_VISITED ^ NS_EVENT_STATE_UNVISITED;
-    nsAutoScriptBlocker scriptBlocker;
-    doc->ContentStateChanged(element, changedState);
+  // We have to be very careful here: if aNotify is false we do NOT
+  // want to call UpdateState, because that will call into LinkState()
+  // and try to start off loads, etc.  But ResetLinkState is called
+  // with aNotify false when things are in inconsistent states, so
+  // we'll get confused in that situation.  Instead, just silently
+  // update the link state on mElement.
+  if (aNotify) {
+    mElement->UpdateState(aNotify);
+  } else {
+    mElement->UpdateLinkState(nsEventStates());
   }
 }
 
 void
 Link::UnregisterFromHistory()
 {
   // If we are not registered, we have nothing to do.
   if (!mRegistered) {
--- a/content/base/src/nsGenConImageContent.cpp
+++ b/content/base/src/nsGenConImageContent.cpp
@@ -50,16 +50,19 @@
 
 class nsGenConImageContent : public nsXMLElement,
                              public nsImageLoadingContent
 {
 public:
   nsGenConImageContent(already_AddRefed<nsINodeInfo> aNodeInfo)
     : nsXMLElement(aNodeInfo)
   {
+    // nsImageLoadingContent starts out broken, so we start out
+    // suppressed to match it.
+    AddStatesSilently(NS_EVENT_STATE_SUPPRESSED);
   }
 
   nsresult Init(imgIRequest* aImageRequest)
   {
     // No need to notify, since we have no frame.
     return UseAsPrimaryRequest(aImageRequest, PR_FALSE);
   }
 
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -530,17 +530,17 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
       aDocument->SetBidiEnabled();
     }
     // Clear the lazy frame construction bits.
     UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
   }
 
   nsNodeUtils::ParentChainChanged(this);
 
-  UpdateEditableState();
+  UpdateEditableState(PR_FALSE);
 
   NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document");
   NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
   NS_POSTCONDITION(aBindingParent == GetBindingParent(),
                    "Bound to wrong binding parent");
 
   return NS_OK;
 }
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -814,23 +814,69 @@ Element::UpdateLinkState(nsEventStates a
                                                     NS_EVENT_STATE_UNVISITED)),
                     "Unexpected link state bits");
   mState =
     (mState & ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) |
     aState;
 }
 
 void
-nsIContent::UpdateEditableState()
-{
+Element::UpdateState(bool aNotify)
+{
+  nsEventStates oldState = mState;
+  mState = IntrinsicState() | (oldState & ESM_MANAGED_STATES);
+  if (aNotify) {
+    nsEventStates changedStates = oldState ^ mState;
+    if (!changedStates.IsEmpty()) {
+      nsIDocument* doc = GetCurrentDoc();
+      if (doc) {
+        nsAutoScriptBlocker scriptBlocker;
+        doc->ContentStateChanged(this, changedStates);
+      }
+    }
+  }
+}
+
+void
+nsIContent::UpdateEditableState(PRBool aNotify)
+{
+  // Guaranteed to be non-element content
+  NS_ASSERTION(!IsElement(), "What happened here?");
   nsIContent *parent = GetParent();
 
   SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
 }
 
+void
+nsGenericElement::UpdateEditableState(PRBool aNotify)
+{
+  nsIContent *parent = GetParent();
+
+  PRBool oldEditable = IsEditable();
+  SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
+  PRBool newEditable = IsEditable();
+  if (oldEditable != newEditable) {
+    if (aNotify) {
+      UpdateState(aNotify);
+    } else {
+      // Avoid calling UpdateState in this very common case, because
+      // this gets called for pretty much every single element on
+      // insertion into the document and UpdateState can be slow for
+      // some kinds of elements even when not notifying.
+      if (oldEditable) {
+        RemoveStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
+        AddStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
+      } else {
+        RemoveStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
+        AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
+      }
+    }
+  }
+}
+
 nsIContent*
 nsIContent::FindFirstNonNativeAnonymous() const
 {
   // This handles also nested native anonymous content.
   for (const nsIContent *content = this; content;
        content = content->GetBindingParent()) {
     if (!content->IsInNativeAnonymousSubtree()) {
       // Oops, this function signature allows casting const to
@@ -3029,17 +3075,17 @@ nsGenericElement::BindToTree(nsIDocument
         if (binding) {
           rv = BindNodesInInsertPoints(binding, this, aDocument);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       }
     }
   }
 
-  UpdateEditableState();
+  UpdateEditableState(PR_FALSE);
 
   // Now recurse into our kids
   for (nsIContent* child = GetFirstChild(); child;
        child = child->GetNextSibling()) {
     rv = child->BindToTree(aDocument, this, aBindingParent,
                            aCompileEventHandlers);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -4663,23 +4709,16 @@ nsGenericElement::SetAttrAndNotify(PRInt
                                    PRBool aNotify,
                                    const nsAString* aValueForAfterSetAttr)
 {
   nsresult rv;
 
   nsIDocument* document = GetCurrentDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
-  // When notifying, make sure to keep track of states whose value
-  // depends solely on the value of an attribute.
-  nsEventStates stateMask;
-  if (aNotify) {
-    stateMask = IntrinsicState();
-  }
-
   nsMutationGuard::DidMutate();
 
   if (aNamespaceID == kNameSpaceID_None) {
     // XXXbz Perhaps we should push up the attribute mapping function
     // stuff to nsGenericElement?
     if (!IsAttributeMapped(aName) ||
         !SetMappedAttribute(document, aName, aParsedValue, &rv)) {
       rv = mAttrsAndChildren.SetAndTakeAttr(aName, aParsedValue);
@@ -4701,21 +4740,19 @@ nsGenericElement::SetAttrAndNotify(PRInt
       nsRefPtr<nsXBLBinding> binding =
         ownerDoc->BindingManager()->GetBinding(this);
       if (binding) {
         binding->AttributeChanged(aName, aNamespaceID, PR_FALSE, aNotify);
       }
     }
   }
 
+  UpdateState(aNotify);
+
   if (aNotify) {
-    stateMask ^= IntrinsicState();
-    if (document && !stateMask.IsEmpty()) {
-      document->ContentStateChanged(this, stateMask);
-    }
     nsNodeUtils::AttributeChanged(this, aNamespaceID, aName, aModType);
   }
 
   if (aNamespaceID == kNameSpaceID_XMLEvents && 
       aName == nsGkAtoms::event && mNodeInfo->GetDocument()) {
     mNodeInfo->GetDocument()->AddXMLEventsContent(this);
   }
   if (aValueForAfterSetAttr) {
@@ -4899,23 +4936,16 @@ nsGenericElement::UnsetAttr(PRInt32 aNam
   nsIDocument *document = GetCurrentDoc();    
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNameSpaceID, aName,
                                      nsIDOMMutationEvent::REMOVAL);
   }
 
-  // When notifying, make sure to keep track of states whose value
-  // depends solely on the value of an attribute.
-  nsEventStates stateMask;
-  if (aNotify) {
-    stateMask = IntrinsicState();
-  }
-
   PRBool hasMutationListeners = aNotify &&
     nsContentUtils::HasMutationListeners(this,
                                          NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
                                          this);
 
   // Grab the attr node if needed before we remove it from the attr map
   nsCOMPtr<nsIDOMAttr> attrNode;
   if (hasMutationListeners) {
@@ -4945,21 +4975,19 @@ nsGenericElement::UnsetAttr(PRInt32 aNam
       nsRefPtr<nsXBLBinding> binding =
         ownerDoc->BindingManager()->GetBinding(this);
       if (binding) {
         binding->AttributeChanged(aName, aNameSpaceID, PR_TRUE, aNotify);
       }
     }
   }
 
+  UpdateState(aNotify);
+
   if (aNotify) {
-    stateMask ^= IntrinsicState();
-    if (document && !stateMask.IsEmpty()) {
-      document->ContentStateChanged(this, stateMask);
-    }
     nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
                                   nsIDOMMutationEvent::REMOVAL);
   }
 
   rv = AfterSetAttr(aNameSpaceID, aName, nsnull, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (hasMutationListeners) {
@@ -5080,17 +5108,17 @@ nsGenericElement::List(FILE* out, PRInt3
   fputs(aPrefix.get(), out);
 
   fputs(NS_LossyConvertUTF16toASCII(mNodeInfo->QualifiedName()).get(), out);
 
   fprintf(out, "@%p", (void *)this);
 
   ListAttributes(out);
 
-  fprintf(out, " intrinsicstate=[%llx]", IntrinsicState().GetInternalValue());
+  fprintf(out, " state=[%llx]", State().GetInternalValue());
   fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
   fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame()));
   fprintf(out, " refcount=%d<", mRefCnt.get());
 
   PRUint32 i, length = GetChildCount();
   if (length > 0) {
     fputs("\n", out);
 
--- a/content/base/src/nsGenericElement.h
+++ b/content/base/src/nsGenericElement.h
@@ -350,16 +350,18 @@ public:
     nsContentUtils::GetNodeTextContent(this, PR_TRUE, aTextContent);
   }
   virtual nsresult SetTextContent(const nsAString& aTextContent)
   {
     return nsContentUtils::SetNodeTextContent(this, aTextContent, PR_FALSE);
   }
 
   // nsIContent interface methods
+  virtual void UpdateEditableState(PRBool aNotify);
+
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
   virtual already_AddRefed<nsINodeList> GetChildren(PRUint32 aFilter);
   virtual nsIAtom *GetClassAttributeName() const;
   virtual already_AddRefed<nsINodeInfo> GetExistingAttrNameFromQName(const nsAString& aStr) const;
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -789,18 +789,16 @@ nsImageLoadingContent::UpdateImageState(
     return;
   }
   
   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
   if (!thisContent) {
     return;
   }
 
-  nsEventStates oldState = ImageState();
-
   mLoading = mBroken = mUserDisabled = mSuppressed = PR_FALSE;
   
   // If we were blocked by server-based content policy, we claim to be
   // suppressed.  If we were blocked by type-based content policy, we claim to
   // be user-disabled.  Otherwise, claim to be broken.
   if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) {
     mSuppressed = PR_TRUE;
   } else if (mImageBlockingStatus == nsIContentPolicy::REJECT_TYPE) {
@@ -813,27 +811,18 @@ nsImageLoadingContent::UpdateImageState(
     nsresult rv = mCurrentRequest->GetImageStatus(&currentLoadStatus);
     if (NS_FAILED(rv) || (currentLoadStatus & imgIRequest::STATUS_ERROR)) {
       mBroken = PR_TRUE;
     } else if (!(currentLoadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
       mLoading = PR_TRUE;
     }
   }
 
-  if (aNotify) {
-    nsIDocument* doc = thisContent->GetCurrentDoc();
-    if (doc) {
-      NS_ASSERTION(thisContent->IsInDoc(), "Something is confused");
-      nsEventStates changedBits = oldState ^ ImageState();
-      if (!changedBits.IsEmpty()) {
-        nsAutoScriptBlocker scriptBlocker;
-        doc->ContentStateChanged(thisContent, changedBits);
-      }
-    }
-  }
+  NS_ASSERTION(thisContent->IsElement(), "Not an element?");
+  thisContent->AsElement()->UpdateState(aNotify);
 }
 
 void
 nsImageLoadingContent::CancelImageRequests(PRBool aNotify)
 {
   AutoStateChanger changer(this, aNotify);
   ClearPendingRequest(NS_BINDING_ABORTED);
   ClearCurrentRequest(NS_BINDING_ABORTED);
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -332,30 +332,28 @@ nsPluginCrashedEvent::Run()
 class AutoNotifier {
   public:
     AutoNotifier(nsObjectLoadingContent* aContent, PRBool aNotify) :
       mContent(aContent), mNotify(aNotify) {
         mOldType = aContent->Type();
         mOldState = aContent->ObjectState();
     }
     ~AutoNotifier() {
-      if (mNotify) {
-        mContent->NotifyStateChanged(mOldType, mOldState, PR_FALSE);
-      }
+      mContent->NotifyStateChanged(mOldType, mOldState, PR_FALSE, mNotify);
     }
 
     /**
      * Send notifications now, ignoring the value of mNotify. The new type and
      * state is saved, and the destructor will notify again if mNotify is true
      * and the values changed.
      */
     void Notify() {
       NS_ASSERTION(mNotify, "Should not notify when notify=false");
 
-      mContent->NotifyStateChanged(mOldType, mOldState, PR_TRUE);
+      mContent->NotifyStateChanged(mOldType, mOldState, PR_TRUE, PR_TRUE);
       mOldType = mContent->Type();
       mOldState = mContent->ObjectState();
     }
 
   private:
     nsObjectLoadingContent*            mContent;
     PRBool                             mNotify;
     nsObjectLoadingContent::ObjectType mOldType;
@@ -1634,26 +1632,40 @@ nsObjectLoadingContent::UnloadContent()
   }
   mType = eType_Null;
   mUserDisabled = mSuppressed = PR_FALSE;
   mFallbackReason = ePluginOtherState;
 }
 
 void
 nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType,
-                                          nsEventStates aOldState, PRBool aSync)
+                                           nsEventStates aOldState,
+                                           PRBool aSync,
+                                           PRBool aNotify)
 {
   LOG(("OBJLC [%p]: Notifying about state change: (%u, %llx) -> (%u, %llx) (sync=%i)\n",
        this, aOldType, aOldState.GetInternalValue(), mType,
        ObjectState().GetInternalValue(), aSync));
 
   nsCOMPtr<nsIContent> thisContent = 
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "must be a content");
 
+  NS_ASSERTION(thisContent->IsElement(), "Not an element?");
+
+  // Unfortunately, we do some state changes without notifying
+  // (e.g. in Fallback when canceling image requests), so we have to
+  // manually notify object state changes.
+  thisContent->AsElement()->UpdateState(false);
+
+  if (!aNotify) {
+    // We're done here
+    return;
+  }
+
   nsIDocument* doc = thisContent->GetCurrentDoc();
   if (!doc) {
     return; // Nothing to do
   }
 
   nsEventStates newState = ObjectState();
 
   if (newState != aOldState) {
@@ -1661,18 +1673,17 @@ nsObjectLoadingContent::NotifyStateChang
     NS_ASSERTION(thisContent->IsInDoc(), "Something is confused");
     nsEventStates changedBits = aOldState ^ newState;
 
     {
       nsAutoScriptBlocker scriptBlocker;
       doc->ContentStateChanged(thisContent, changedBits);
     }
     if (aSync) {
-      // Make sure that frames are actually constructed, and do it after
-      // EndUpdate was called.
+      // Make sure that frames are actually constructed immediately.
       doc->FlushPendingNotifications(Flush_Frames);
     }
   } else if (aOldType != mType) {
     // If our state changed, then we already recreated frames
     // Otherwise, need to do that here
     nsCOMPtr<nsIPresShell> shell = doc->GetShell();
     if (shell) {
       shell->RecreateFramesFor(thisContent);
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -251,19 +251,20 @@ class nsObjectLoadingContent : public ns
     /**
      * Notifies document observes about a new type/state of this object.
      * Triggers frame construction as needed. mType must be set correctly when
      * this method is called. This method is cheap if the type and state didn't
      * actually change.
      *
      * @param aSync If a synchronous frame construction is required. If false,
      *              the construction may either be sync or async.
+     * @param aNotify if false, only need to update the state of our element.
      */
     void NotifyStateChanged(ObjectType aOldType, nsEventStates aOldState,
-                            PRBool aSync);
+                            PRBool aSync, PRBool aNotify);
 
     /**
      * Fires the "Plugin not found" event. This function doesn't do any checks
      * whether it should be fired, the caller should do that.
      */
     static void FirePluginError(nsIContent* thisContent, PluginSupportState state);
 
     ObjectType GetTypeOfContent(const nsCString& aMIMEType);
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -934,27 +934,26 @@ nsGenericHTMLElement::SetDraggable(PRBoo
 
 PRBool
 nsGenericHTMLElement::InNavQuirksMode(nsIDocument* aDoc)
 {
   return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
 }
 
 void
-nsGenericHTMLElement::UpdateEditableState()
+nsGenericHTMLElement::UpdateEditableState(PRBool aNotify)
 {
   // XXX Should we do this only when in a document?
   ContentEditableTristate value = GetContentEditableValue();
   if (value != eInherit) {
-    SetEditableFlag(!!value);
-
+    DoSetEditableFlag(!!value, aNotify);
     return;
   }
 
-  nsStyledElement::UpdateEditableState();
+  nsStyledElement::UpdateEditableState(aNotify);
 }
 
 nsresult
 nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                  nsIContent* aBindingParent,
                                  PRBool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent,
@@ -1708,45 +1707,44 @@ nsGenericHTMLElement::MapCommonAttribute
       if (aAttributes->IndexOfAttr(nsGkAtoms::hidden, kNameSpaceID_None) >= 0) {
         display->SetIntValue(NS_STYLE_DISPLAY_NONE, eCSSUnit_Enumerated);
       }
     }
   }
 }
 
 void
-nsGenericHTMLFormElement::UpdateEditableFormControlState()
+nsGenericHTMLFormElement::UpdateEditableFormControlState(PRBool aNotify)
 {
   // nsCSSFrameConstructor::MaybeConstructLazily is based on the logic of this
   // function, so should be kept in sync with that.
 
   ContentEditableTristate value = GetContentEditableValue();
   if (value != eInherit) {
-    SetEditableFlag(!!value);
-
+    DoSetEditableFlag(!!value, aNotify);
     return;
   }
 
   nsIContent *parent = GetParent();
 
   if (parent && parent->HasFlag(NODE_IS_EDITABLE)) {
-    SetEditableFlag(PR_TRUE);
+    DoSetEditableFlag(PR_TRUE, aNotify);
     return;
   }
 
   if (!IsTextControl(PR_FALSE)) {
-    SetEditableFlag(PR_FALSE);
+    DoSetEditableFlag(PR_FALSE, aNotify);
     return;
   }
 
   // If not contentEditable we still need to check the readonly attribute.
   PRBool roState;
   GetBoolAttr(nsGkAtoms::readonly, &roState);
 
-  SetEditableFlag(!roState);
+  DoSetEditableFlag(!roState, aNotify);
 }
 
 
 /* static */ const nsGenericHTMLElement::MappedAttributeEntry
 nsGenericHTMLElement::sCommonAttributeMap[] = {
   { &nsGkAtoms::contenteditable },
   { &nsGkAtoms::lang },
   { &nsGkAtoms::hidden },
@@ -2405,16 +2403,19 @@ nsGenericHTMLElement::GetIsContentEditab
 
 NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex)
 
 nsGenericHTMLFormElement::nsGenericHTMLFormElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
   , mForm(nsnull)
   , mFieldSet(nsnull)
 {
+  // We should add the NS_EVENT_STATE_ENABLED bit here as needed, but
+  // that depends on our type, which is not initialized yet.  So we
+  // have to do this in subclasses.
 }
 
 nsGenericHTMLFormElement::~nsGenericHTMLFormElement()
 {
   if (mFieldSet) {
     static_cast<nsHTMLFieldSetElement*>(mFieldSet)->RemoveElement(this);
   }
 
@@ -2559,17 +2560,17 @@ nsGenericHTMLFormElement::BindToTree(nsI
   // We should not call UpdateFormOwner if none of these conditions are
   // fulfilled.
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? !!GetCurrentDoc()
                                                   : !!aParent) {
     UpdateFormOwner(true, nsnull);
   }
 
   // Set parent fieldset which should be used for the disabled state.
-  UpdateFieldSet();
+  UpdateFieldSet(PR_FALSE);
 
   return NS_OK;
 }
 
 void
 nsGenericHTMLFormElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
 {
   // Save state before doing anything
@@ -2584,29 +2585,34 @@ nsGenericHTMLFormElement::UnbindFromTree
       // Recheck whether we should still have an mForm.
       if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
           !FindAncestorForm(mForm)) {
         ClearForm(PR_TRUE);
       } else {
         UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
       }
     }
+
+    if (!mForm) {
+      // Our novalidate state might have changed
+      UpdateState(false);
+    }
   }
 
   // We have to remove the form id observer if there was one.
   // We will re-add one later if needed (during bind to tree).
   if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
                                       nsGkAtoms::form)) {
     RemoveFormIdObserver();
   }
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   // The element might not have a fieldset anymore.
-  UpdateFieldSet();
+  UpdateFieldSet(PR_FALSE);
 }
 
 nsresult
 nsGenericHTMLFormElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                         const nsAString* aValue, PRBool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     nsAutoString tmp;
@@ -2617,18 +2623,16 @@ nsGenericHTMLFormElement::BeforeSetAttr(
       GetAttr(kNameSpaceID_None, aName, tmp);
 
       if (!tmp.IsEmpty()) {
         mForm->RemoveElementFromTable(this, tmp);
       }
     }
 
     if (mForm && aName == nsGkAtoms::type) {
-      nsIDocument* doc = GetCurrentDoc();
-
       GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
 
       if (!tmp.IsEmpty()) {
         mForm->RemoveElementFromTable(this, tmp);
       }
 
       GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
 
@@ -2637,20 +2641,18 @@ nsGenericHTMLFormElement::BeforeSetAttr(
       }
 
       mForm->RemoveElement(this, false);
 
       // Removing the element from the form can make it not be the default
       // control anymore.  Go ahead and notify on that change, though we might
       // end up readding and becoming the default control again in
       // AfterSetAttr.
-      if (doc && aNotify) {
-        nsAutoScriptBlocker scriptBlocker;
-        doc->ContentStateChanged(this, NS_EVENT_STATE_DEFAULT);
-      }
+      // FIXME: Bug 656197
+      UpdateState(aNotify);
     }
 
     if (aName == nsGkAtoms::form) {
       // If @form isn't set or set to the empty string, there were no observer
       // so we don't have to remove it.
       if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
                                           nsGkAtoms::form)) {
         // The current form id observer is no longer needed.
@@ -2674,17 +2676,16 @@ nsGenericHTMLFormElement::AfterSetAttr(P
     if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
         aValue) {
       if (!aValue->IsEmpty()) {
         mForm->AddElementToTable(this, *aValue);
       }
     }
 
     if (mForm && aName == nsGkAtoms::type) {
-      nsIDocument* doc = GetDocument();
       nsAutoString tmp;
 
       GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
 
       if (!tmp.IsEmpty()) {
         mForm->AddElementToTable(this, tmp);
       }
 
@@ -2695,19 +2696,17 @@ nsGenericHTMLFormElement::AfterSetAttr(P
       }
 
       mForm->AddElement(this, false, aNotify);
 
       // Adding the element to the form can make it be the default control .
       // Go ahead and notify on that change.
       // Note: no need to notify on CanBeDisabled(), since type attr
       // changes can't affect that.
-      if (doc && aNotify) {
-        doc->ContentStateChanged(this, NS_EVENT_STATE_DEFAULT);
-      }
+      UpdateState(aNotify);
     }
 
     if (aName == nsGkAtoms::form) {
       // We need a new form id observer.
       nsIDocument* doc = GetCurrentDoc();
       if (doc) {
         Element* formIdElement = nsnull;
         if (aValue && !aValue->IsEmpty()) {
@@ -2908,29 +2907,23 @@ nsGenericHTMLFormElement::FormIdUpdated(
 
 void
 nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
                                           Element* aFormIdElement)
 {
   NS_PRECONDITION(!aBindToTree || !aFormIdElement,
                   "aFormIdElement shouldn't be set if aBindToTree is true!");
 
+  PRBool needStateUpdate = PR_FALSE;
   if (!aBindToTree) {
-    PRBool wasDefaultSubmit = mForm && mForm->IsDefaultSubmitElement(this);
+    needStateUpdate = mForm && mForm->IsDefaultSubmitElement(this);
     ClearForm(PR_TRUE);
-    if (wasDefaultSubmit) {
-      nsIDocument* doc = GetCurrentDoc();
-      if (doc) {
-        nsAutoScriptBlocker scriptBlocker;
-        doc->ContentStateChanged(this, NS_EVENT_STATE_DEFAULT);
-      }
-    }
   }
 
-  bool hadForm = mForm;
+  nsHTMLFormElement *oldForm = mForm;
 
   if (!mForm) {
     // If @form is set, we have to use that to find the form.
     nsAutoString formId;
     if (GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId)) {
       if (!formId.IsEmpty()) {
         Element* element = nsnull;
 
@@ -2967,72 +2960,76 @@ nsGenericHTMLFormElement::UpdateFormOwne
     // Now we need to add ourselves to the form
     nsAutoString nameVal, idVal;
     GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
     GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
 
     SetFlags(ADDED_TO_FORM);
 
     // Notify only if we just found this mForm.
-    mForm->AddElement(this, true, !hadForm);
+    mForm->AddElement(this, true, oldForm == nsnull);
 
     if (!nameVal.IsEmpty()) {
       mForm->AddElementToTable(this, nameVal);
     }
 
     if (!idVal.IsEmpty()) {
       mForm->AddElementToTable(this, idVal);
     }
   }
+
+  if (mForm != oldForm || needStateUpdate) {
+    UpdateState(true);
+  }
 }
 
 void
-nsGenericHTMLFormElement::UpdateFieldSet()
+nsGenericHTMLFormElement::UpdateFieldSet(PRBool aNotify)
 {
   nsIContent* parent = nsnull;
   nsIContent* prev = nsnull;
 
   for (parent = GetParent(); parent;
        prev = parent, parent = parent->GetParent()) {
     if (parent->IsHTML(nsGkAtoms::fieldset)) {
       nsHTMLFieldSetElement* fieldset =
         static_cast<nsHTMLFieldSetElement*>(parent);
 
       if (!prev || fieldset->GetFirstLegend() != prev) {
+        if (mFieldSet == fieldset) {
+          // We already have the right fieldset;
+          return;
+        }
+
         if (mFieldSet) {
           static_cast<nsHTMLFieldSetElement*>(mFieldSet)->RemoveElement(this);
         }
         mFieldSet = fieldset;
         fieldset->AddElement(this);
+
+        // The disabled state may have changed
+        FieldSetDisabledChanged(aNotify);
         return;
       }
     }
   }
 
   // No fieldset found.
   if (mFieldSet) {
     static_cast<nsHTMLFieldSetElement*>(mFieldSet)->RemoveElement(this);
+    mFieldSet = nsnull;
+    // The disabled state may have changed
+    FieldSetDisabledChanged(aNotify);
   }
-  mFieldSet = nsnull;
 }
 
 void
-nsGenericHTMLFormElement::FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify)
+nsGenericHTMLFormElement::FieldSetDisabledChanged(PRBool aNotify)
 {
-  if (!aNotify) {
-    return;
-  }
-
-  aStates |= NS_EVENT_STATE_DISABLED | NS_EVENT_STATE_ENABLED;
-
-  nsIDocument* doc = GetCurrentDoc();
-  if (doc) {
-    nsAutoScriptBlocker scriptBlocker;
-    doc->ContentStateChanged(this, aStates);
-  }
+  UpdateState(aNotify);
 }
 
 //----------------------------------------------------------------------
 
 nsGenericHTMLFrameElement::~nsGenericHTMLFrameElement()
 {
   if (mFrameLoader) {
     mFrameLoader->Destroy();
@@ -3551,30 +3548,23 @@ nsGenericHTMLElement::IsEditableRoot() c
 static void
 MakeContentDescendantsEditable(nsIContent *aContent, nsIDocument *aDocument)
 {
   // If aContent is not an element, we just need to update its
   // internal editable state and don't need to notify anyone about
   // that.  For elements, we need to send a ContentStateChanged
   // notification.
   if (!aContent->IsElement()) {
-    aContent->UpdateEditableState();
+    aContent->UpdateEditableState(PR_FALSE);
     return;
   }
 
   Element *element = aContent->AsElement();
-  nsEventStates stateBefore = element->State();
-
-  element->UpdateEditableState();
-
-  if (aDocument && stateBefore != element->State()) {
-    aDocument->ContentStateChanged(element,
-                                   NS_EVENT_STATE_MOZ_READONLY |
-                                   NS_EVENT_STATE_MOZ_READWRITE);
-  }
+
+  element->UpdateEditableState(PR_TRUE);
 
   for (nsIContent *child = aContent->GetFirstChild();
        child;
        child = child->GetNextSibling()) {
     if (!child->HasAttr(kNameSpaceID_None, nsGkAtoms::contenteditable)) {
       MakeContentDescendantsEditable(child, aDocument);
     }
   }
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -194,17 +194,23 @@ public:
   PRBool CheckHandleEventForAnchorsPreconditions(nsEventChainVisitor& aVisitor);
   nsresult PreHandleEventForAnchors(nsEventChainPreVisitor& aVisitor);
   nsresult PostHandleEventForAnchors(nsEventChainPostVisitor& aVisitor);
   PRBool IsHTMLLink(nsIURI** aURI) const;
 
   // HTML element methods
   void Compact() { mAttrsAndChildren.Compact(); }
 
-  virtual void UpdateEditableState();
+  virtual void UpdateEditableState(PRBool aNotify);
+
+  // Helper for setting our editable flag and notifying
+  void DoSetEditableFlag(PRBool aEditable, bool aNotify) {
+    SetEditableFlag(aEditable);
+    UpdateState(aNotify);
+  }
 
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
 
   NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
@@ -865,32 +871,28 @@ public:
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
 
   virtual bool IsDisabled() const {
     return HasAttr(kNameSpaceID_None, nsGkAtoms::disabled) ||
            (mFieldSet && mFieldSet->IsDisabled());
   }
 
   /**
-   * This callback is called by a fieldest on all it's elements whenever it's
-   * disabled attribute is changed so the element knows it's disabled state
+   * This callback is called by a fieldest on all its elements whenever its
+   * disabled attribute is changed so the element knows its disabled state
    * might have changed.
    *
-   * @param aStates States for which a change should be notified.
-   * @note Classes redefining this method should not call ContentStatesChanged
-   * but they should pass aStates instead.
+   * @note Classes redefining this method should not do any content
+   * state updates themselves but should just make sure to call into
+   * nsGenericHTMLFormElement::FieldSetDisabledChanged.
    */
-  virtual void FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify);
+  virtual void FieldSetDisabledChanged(PRBool aNotify);
 
   void FieldSetFirstLegendChanged(PRBool aNotify) {
-    UpdateFieldSet();
-
-    // The disabled state may have change because the element might not be in
-    // the first legend anymore.
-    FieldSetDisabledChanged(nsEventStates(), aNotify);
+    UpdateFieldSet(aNotify);
   }
 
   /**
    * This callback is called by a fieldset on all it's elements when it's being
    * destroyed. When called, the elements should check that aFieldset is there
    * first parent fieldset and null mFieldset in that case only.
    *
    * @param aFieldSet The fieldset being removed.
@@ -911,17 +913,17 @@ public:
 
 protected:
   virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                  const nsAString* aValue, PRBool aNotify);
 
   virtual nsresult AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                 const nsAString* aValue, PRBool aNotify);
 
-  void UpdateEditableFormControlState();
+  void UpdateEditableFormControlState(PRBool aNotify);
 
   /**
    * This method will update the form owner, using @form or looking to a parent.
    *
    * @param aBindToTree Whether the element is being attached to the tree.
    * @param aFormIdElement The element associated with the id in @form. If
    * aBindToTree is false, aFormIdElement *must* contain the element associated
    * with the id in @form. Otherwise, it *must* be null.
@@ -929,17 +931,17 @@ protected:
    * @note Callers of UpdateFormOwner have to be sure the element is in a
    * document (GetCurrentDoc() != nsnull).
    */
   void UpdateFormOwner(bool aBindToTree, Element* aFormIdElement);
 
   /**
    * This method will update mFieldset and set it to the first fieldset parent.
    */
-  void UpdateFieldSet();
+  void UpdateFieldSet(PRBool aNotify);
 
   /**
    * Add a form id observer which will observe when the element with the id in
    * @form will change.
    *
    * @return The element associated with the current id in @form (may be null).
    */
   Element* AddFormIdObserver();
--- a/content/html/content/src/nsHTMLButtonElement.cpp
+++ b/content/html/content/src/nsHTMLButtonElement.cpp
@@ -129,16 +129,22 @@ public:
   virtual PRBool IsHTMLFocusable(PRBool aWithMouse, PRBool *aIsFocusable, PRInt32 *aTabIndex);
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
   virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
 
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent,
+                              PRBool aCompileEventHandlers);
+  virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
+                              PRBool aNullParent = PR_TRUE);
+
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual void DoneCreatingElement();
   virtual nsXPCClassInfo* GetClassInfo();
 
 protected:
   PRUint8 mType;
   PRPackedBool mDisabledChanged;
   PRPackedBool mInInternalActivate;
@@ -162,16 +168,19 @@ nsHTMLButtonElement::nsHTMLButtonElement
   : nsGenericHTMLFormElement(aNodeInfo),
     mType(kButtonDefaultType->value),
     mDisabledChanged(PR_FALSE),
     mInInternalActivate(PR_FALSE),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT))
 {
   // <button> is always barred from constraint validation.
   SetBarredFromConstraintValidation(PR_TRUE);
+
+  // Set up our default state: enabled
+  AddStatesSilently(NS_EVENT_STATE_ENABLED);
 }
 
 nsHTMLButtonElement::~nsHTMLButtonElement()
 {
 }
 
 // nsISupports
 
@@ -482,16 +491,41 @@ nsHTMLButtonElement::PostHandleEvent(nsE
     // Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
     mForm->FlushPendingSubmission();
   } //if
 
   return rv;
 }
 
 nsresult
+nsHTMLButtonElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                                nsIContent* aBindingParent,
+                                PRBool aCompileEventHandlers)
+{
+  nsresult rv = nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
+                                                     aBindingParent,
+                                                     aCompileEventHandlers);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Update our state; we may now be the default submit element
+  UpdateState(false);
+
+  return NS_OK;
+}
+
+void
+nsHTMLButtonElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
+{
+  nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
+
+  // Update our state; we may no longer be the default submit element
+  UpdateState(false);
+}
+
+nsresult
 nsHTMLButtonElement::GetDefaultValue(nsAString& aDefaultValue)
 {
   GetAttr(kNameSpaceID_None, nsGkAtoms::value, aDefaultValue);
   return NS_OK;
 }
 
 nsresult
 nsHTMLButtonElement::SetDefaultValue(const nsAString& aDefaultValue)
@@ -569,32 +603,23 @@ nsHTMLButtonElement::BeforeSetAttr(PRInt
   return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
                                                  aValue, aNotify);
 }
 
 nsresult
 nsHTMLButtonElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                   const nsAString* aValue, PRBool aNotify)
 {
-  nsEventStates states;
-
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::type) {
       if (!aValue) {
         mType = kButtonDefaultType->value;
       }
 
-      states |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
-    }
-
-    if (aNotify && !states.IsEmpty()) {
-      nsIDocument* doc = GetCurrentDoc();
-      if (doc) {
-        doc->ContentStateChanged(this, states);
-      }
+      UpdateState(aNotify);
     }
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
                                                 aValue, aNotify);
 }
 
 NS_IMETHODIMP
--- a/content/html/content/src/nsHTMLFieldSetElement.cpp
+++ b/content/html/content/src/nsHTMLFieldSetElement.cpp
@@ -49,16 +49,19 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet)
 
 nsHTMLFieldSetElement::nsHTMLFieldSetElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLFormElement(aNodeInfo)
   , mElements(nsnull)
   , mFirstLegend(nsnull)
 {
   // <fieldset> is always barred from constraint validation.
   SetBarredFromConstraintValidation(PR_TRUE);
+
+  // We start out enabled
+  AddStatesSilently(NS_EVENT_STATE_ENABLED);
 }
 
 nsHTMLFieldSetElement::~nsHTMLFieldSetElement()
 {
   PRUint32 length = mDependentElements.Length();
   for (PRUint32 i=0; i<length; ++i) {
     mDependentElements[i]->ForgetFieldSet(this);
   }
@@ -122,17 +125,17 @@ nsHTMLFieldSetElement::AfterSetAttr(PRIn
     if (!mElements) {
       mElements = new nsContentList(this, MatchListedElements, nsnull, nsnull,
                                     PR_TRUE);
     }
 
     PRUint32 length = mElements->Length(PR_TRUE);
     for (PRUint32 i=0; i<length; ++i) {
       static_cast<nsGenericHTMLFormElement*>(mElements->GetNodeAt(i))
-        ->FieldSetDisabledChanged(nsEventStates(), aNotify);
+        ->FieldSetDisabledChanged(aNotify);
     }
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
                                                 aValue, aNotify);
 }
 
 // nsIDOMHTMLFieldSetElement
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -377,31 +377,24 @@ nsHTMLFormElement::SetAttr(PRInt32 aName
 
 nsresult
 nsHTMLFormElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                 const nsAString* aValue, PRBool aNotify)
 {
   if (aName == nsGkAtoms::novalidate && aNameSpaceID == kNameSpaceID_None) {
     // Update all form elements states because they might be [no longer]
     // affected by :-moz-ui-valid or :-moz-ui-invalid.
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      for (PRUint32 i = 0, length = mControls->mElements.Length();
-           i < length; ++i) {
-        doc->ContentStateChanged(mControls->mElements[i],
-                                 NS_EVENT_STATE_MOZ_UI_VALID |
-                                 NS_EVENT_STATE_MOZ_UI_INVALID);
-      }
+    for (PRUint32 i = 0, length = mControls->mElements.Length();
+         i < length; ++i) {
+      mControls->mElements[i]->UpdateState(true);
+    }
 
-      for (PRUint32 i = 0, length = mControls->mNotInElements.Length();
-           i < length; ++i) {
-        doc->ContentStateChanged(mControls->mNotInElements[i],
-                                 NS_EVENT_STATE_MOZ_UI_VALID |
-                                 NS_EVENT_STATE_MOZ_UI_INVALID);
-      }
+    for (PRUint32 i = 0, length = mControls->mNotInElements.Length();
+         i < length; ++i) {
+      mControls->mNotInElements[i]->UpdateState(true);
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify);
 }
 
 NS_IMPL_STRING_ATTR(nsHTMLFormElement, AcceptCharset, acceptcharset)
 NS_IMPL_ACTION_ATTR(nsHTMLFormElement, Action, action)
@@ -500,18 +493,17 @@ MarkOrphans(const nsTArray<nsGenericHTML
 
 static void
 CollectOrphans(nsINode* aRemovalRoot, nsTArray<nsGenericHTMLFormElement*> aArray
 #ifdef DEBUG
                , nsIDOMHTMLFormElement* aThisForm
 #endif
                )
 {
-  // Prepare document update batch.
-  nsIDocument* doc = aArray.IsEmpty() ? nsnull : aArray[0]->GetCurrentDoc();
+  // Put a script blocker around all the notifications we're about to do.
   nsAutoScriptBlocker scriptBlocker;
 
   // Walk backwards so that if we remove elements we can just keep iterating
   PRUint32 length = aArray.Length();
   for (PRUint32 i = length; i > 0; --i) {
     nsGenericHTMLFormElement* node = aArray[i-1];
 
     // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
@@ -522,30 +514,18 @@ CollectOrphans(nsINode* aRemovalRoot, ns
 #ifdef DEBUG
     PRBool removed = PR_FALSE;
 #endif
     if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
       node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
       if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
         node->ClearForm(PR_TRUE);
 
-        // When a form control loses its form owner, :-moz-ui-invalid and
-        // :-moz-ui-valid might not apply any more.
-        nsEventStates states = NS_EVENT_STATE_MOZ_UI_VALID |
-                               NS_EVENT_STATE_MOZ_UI_INVALID;
-
-        // In addition, submit controls shouldn't have
-        // NS_EVENT_STATE_MOZ_SUBMITINVALID applying if they do not have a form.
-        if (node->IsSubmitControl()) {
-          states |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
-        }
-
-        if (doc) {
-          doc->ContentStateChanged(node, states);
-        }
+        // When a form control loses its form owner, its state can change.
+        node->UpdateState(true);
 #ifdef DEBUG
         removed = PR_TRUE;
 #endif
       }
     }
 
 #ifdef DEBUG
     if (!removed) {
@@ -1198,17 +1178,17 @@ nsHTMLFormElement::AddElement(nsGenericH
     // The new child is the new first submit in its list if the firstSubmitSlot
     // is currently empty or if the child is before what's currently in the
     // slot.  Note that if we already have a control in firstSubmitSlot and
     // we're appending this element can't possibly replace what's currently in
     // the slot.  Also note that aChild can't become the mDefaultSubmitElement
     // unless it replaces what's in the slot.  If it _does_ replace what's in
     // the slot, it becomes the default submit if either the default submit is
     // what's in the slot or the child is earlier than the default submit.
-    nsIFormControl* oldDefaultSubmit = mDefaultSubmitElement;
+    nsGenericHTMLFormElement* oldDefaultSubmit = mDefaultSubmitElement;
     if (!*firstSubmitSlot ||
         (!lastElement &&
          CompareFormControlPosition(aChild, *firstSubmitSlot, this) < 0)) {
       // Update mDefaultSubmitElement if it's currently in a valid state.
       // Valid state means either non-null or null because there are in fact
       // no submit elements around.
       if ((mDefaultSubmitElement ||
            (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
@@ -1221,26 +1201,19 @@ nsHTMLFormElement::AddElement(nsGenericH
     }
     NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
                      mDefaultSubmitElement == mFirstSubmitNotInElements ||
                      !mDefaultSubmitElement,
                      "What happened here?");
 
     // Notify that the state of the previous default submit element has changed
     // if the element which is the default submit element has changed.  The new
-    // default submit element is responsible for its own ContentStateChanged
-    // call.
-    if (aNotify && oldDefaultSubmit &&
-        oldDefaultSubmit != mDefaultSubmitElement) {
-      nsIDocument* document = GetCurrentDoc();
-      if (document) {
-        nsAutoScriptBlocker scriptBlocker;
-        nsCOMPtr<nsIContent> oldElement(do_QueryInterface(oldDefaultSubmit));
-        document->ContentStateChanged(oldElement, NS_EVENT_STATE_DEFAULT);
-      }
+    // default submit element is responsible for its own state update.
+    if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
+      oldDefaultSubmit->UpdateState(aNotify);
     }
   }
 
   // If the element is subject to constraint validaton and is invalid, we need
   // to update our internal counter.
   if (aUpdateValidity) {
     nsCOMPtr<nsIConstraintValidation> cvElmt =
       do_QueryInterface(static_cast<nsGenericHTMLElement*>(aChild));
@@ -1363,22 +1336,17 @@ nsHTMLFormElement::HandleDefaultSubmitRe
   }
 
   NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
                    mDefaultSubmitElement == mFirstSubmitNotInElements,
                    "What happened here?");
 
   // Notify about change if needed.
   if (mDefaultSubmitElement) {
-    nsIDocument* document = GetCurrentDoc();
-    if (document) {
-      nsAutoScriptBlocker scriptBlocker;
-      document->ContentStateChanged(mDefaultSubmitElement,
-                                    NS_EVENT_STATE_DEFAULT);
-    }
+    mDefaultSubmitElement->UpdateState(true);
   }
 }
 
 nsresult
 nsHTMLFormElement::RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
                                           const nsAString& aName)
 {
   return mControls->RemoveElementFromTable(aElement, aName);
@@ -1737,50 +1705,43 @@ nsHTMLFormElement::CheckValidFormSubmiss
 
     if (!CheckFormValidity(invalidElements.get())) {
       // For the first invalid submission, we should update element states.
       // We have to do that _before_ calling the observers so we are sure they
       // will not interfere (like focusing the element).
       if (!mEverTriedInvalidSubmit) {
         mEverTriedInvalidSubmit = true;
 
-        nsIDocument* doc = GetCurrentDoc();
-        if (doc) {
-          /*
-           * We are going to call ContentStateChanged assuming elements want to
-           * be notified because we can't know.
-           * Submissions shouldn't happen during parsing so it _should_ be safe.
-           */
+        /*
+         * We are going to call update states assuming elements want to
+         * be notified because we can't know.
+         * Submissions shouldn't happen during parsing so it _should_ be safe.
+         */
 
-          nsAutoScriptBlocker scriptBlocker;
+        nsAutoScriptBlocker scriptBlocker;
 
-          for (PRUint32 i = 0, length = mControls->mElements.Length();
-               i < length; ++i) {
-            // Input elements can trigger a form submission and we want to
-            // update the style in that case.
-            if (mControls->mElements[i]->IsHTML(nsGkAtoms::input) &&
-                nsContentUtils::IsFocusedContent(mControls->mElements[i])) {
-              static_cast<nsHTMLInputElement*>(mControls->mElements[i])
-                ->UpdateValidityUIBits(true);
-            }
-
-            doc->ContentStateChanged(mControls->mElements[i],
-                                     NS_EVENT_STATE_MOZ_UI_VALID |
-                                     NS_EVENT_STATE_MOZ_UI_INVALID);
+        for (PRUint32 i = 0, length = mControls->mElements.Length();
+             i < length; ++i) {
+          // Input elements can trigger a form submission and we want to
+          // update the style in that case.
+          if (mControls->mElements[i]->IsHTML(nsGkAtoms::input) &&
+              nsContentUtils::IsFocusedContent(mControls->mElements[i])) {
+            static_cast<nsHTMLInputElement*>(mControls->mElements[i])
+              ->UpdateValidityUIBits(true);
           }
 
-          // Because of backward compatibility, <input type='image'> is not in
-          // elements but can be invalid.
-          // TODO: should probably be removed when bug 606491 will be fixed.
-          for (PRUint32 i = 0, length = mControls->mNotInElements.Length();
-               i < length; ++i) {
-            doc->ContentStateChanged(mControls->mNotInElements[i],
-                                     NS_EVENT_STATE_MOZ_UI_VALID |
-                                     NS_EVENT_STATE_MOZ_UI_INVALID);
-          }
+          mControls->mElements[i]->UpdateState(true);
+        }
+
+        // Because of backward compatibility, <input type='image'> is not in
+        // elements but can be invalid.
+        // TODO: should probably be removed when bug 606491 will be fixed.
+        for (PRUint32 i = 0, length = mControls->mNotInElements.Length();
+             i < length; ++i) {
+          mControls->mNotInElements[i]->UpdateState(true);
         }
       }
 
       nsCOMPtr<nsISupports> inst;
       nsCOMPtr<nsIFormSubmitObserver> observer;
       PRBool more = PR_TRUE;
       while (NS_SUCCEEDED(theEnum->HasMoreElements(&more)) && more) {
         theEnum->GetNext(getter_AddRefs(inst));
@@ -1818,46 +1779,39 @@ nsHTMLFormElement::UpdateValidity(PRBool
   // - 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 ContentStateChanged assuming submit controls want to
+   * We are going to update states 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.
    */
 
   nsAutoScriptBlocker scriptBlocker;
 
   // 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->ContentStateChanged(mControls->mElements[i],
-                               NS_EVENT_STATE_MOZ_SUBMITINVALID);
+      mControls->mElements[i]->UpdateState(true);
     }
   }
 
   // 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->ContentStateChanged(mControls->mNotInElements[i],
-                               NS_EVENT_STATE_MOZ_SUBMITINVALID);
+      mControls->mNotInElements[i]->UpdateState(true);
     }
   }
 }
 
 // nsIWebProgressListener
 NS_IMETHODIMP
 nsHTMLFormElement::OnStateChange(nsIWebProgress* aWebProgress,
                                  nsIRequest* aRequest,
--- a/content/html/content/src/nsHTMLImageElement.cpp
+++ b/content/html/content/src/nsHTMLImageElement.cpp
@@ -175,16 +175,18 @@ NS_NewHTMLImageElement(already_AddRefed<
   }
 
   return new nsHTMLImageElement(nodeInfo.forget());
 }
 
 nsHTMLImageElement::nsHTMLImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
 {
+  // We start out broken
+  AddStatesSilently(NS_EVENT_STATE_BROKEN);
 }
 
 nsHTMLImageElement::~nsHTMLImageElement()
 {
   DestroyImageLoadingContent();
 }
 
 
@@ -501,17 +503,20 @@ nsHTMLImageElement::BindToTree(nsIDocume
                                PRBool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+    // FIXME: Bug 660963 it would be nice if we could just have
+    // ClearBrokenState update our state and do it fast...
     ClearBrokenState();
+    RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
     // If loading is temporarily disabled, don't even launch MaybeLoadImage.
     // Otherwise MaybeLoadImage may run later when someone has reenabled
     // loading.
     if (LoadingEnabled()) {
       nsContentUtils::AddScriptRunner(
         NS_NewRunnableMethod(this, &nsHTMLImageElement::MaybeLoadImage));
     }
   }
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -626,16 +626,25 @@ nsHTMLInputElement::nsHTMLInputElement(a
       aFromParser & mozilla::dom::FROM_PARSER_FRAGMENT);
   SET_BOOLBIT(mBitField, BF_CAN_SHOW_INVALID_UI, PR_TRUE);
   SET_BOOLBIT(mBitField, BF_CAN_SHOW_VALID_UI, PR_TRUE);
   mInputData.mState = new nsTextEditorState(this);
   NS_ADDREF(mInputData.mState);
   
   if (!gUploadLastDir)
     nsHTMLInputElement::InitUploadLastDir();
+
+  // Set up our default state.  By default we're enabled (since we're
+  // a control type that can be disabled but not actually disabled
+  // right now), optional, and valid.  We are NOT readwrite by default
+  // until someone calls UpdateEditableState on us, apparently!  Also
+  // by default we don't have to show validity UI and so forth.
+  AddStatesSilently(NS_EVENT_STATE_ENABLED |
+                    NS_EVENT_STATE_OPTIONAL |
+                    NS_EVENT_STATE_VALID);
 }
 
 nsHTMLInputElement::~nsHTMLInputElement()
 {
   DestroyImageLoadingContent();
   FreeData();
 }
 
@@ -795,33 +804,28 @@ nsHTMLInputElement::BeforeSetAttr(PRInt3
                                                  aValue, aNotify);
 }
 
 nsresult
 nsHTMLInputElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                  const nsAString* aValue,
                                  PRBool aNotify)
 {
-  // States changes that have to be passed to ContentStateChanged().
-  nsEventStates states;
-
   if (aNameSpaceID == kNameSpaceID_None) {
     //
     // When name or type changes, radio should be added to radio group.
     // (type changes are handled in the form itself currently)
     // If the parser is not done creating the radio, we also should not do it.
     //
     if ((aName == nsGkAtoms::name ||
          (aName == nsGkAtoms::type && !mForm)) &&
         mType == NS_FORM_INPUT_RADIO &&
         (mForm || !(GET_BOOLBIT(mBitField, BF_PARSER_CREATING)))) {
       AddedToRadioGroup();
       UpdateValueMissingValidityStateForRadio(false);
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_REQUIRED | NS_EVENT_STATE_OPTIONAL;
     }
 
     // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
     // of the element so, if the value of the element is different than @value,
     // we have to re-set it. This is only the case when GetValueMode() returns
     // VALUE_MODE_VALUE.
     if (aName == nsGkAtoms::value &&
         !GetValueChanged() && GetValueMode() == VALUE_MODE_VALUE) {
@@ -878,40 +882,16 @@ nsHTMLInputElement::AfterSetAttr(PRInt32
       } else if (aNotify) {
         // We just got switched to be an image input; we should see
         // whether we have an image to load;
         nsAutoString src;
         if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
           LoadImage(src, PR_FALSE, aNotify);
         }
       }
-
-      // Changing type affects the applicability of some states.  Just notify
-      // on them all now, just in case.  Note that we can't rely on the
-      // notifications LoadImage or CancelImageRequests might have sent, because
-      // those didn't include all the possibly-changed states in the mask. We
-      // have to do this here because we just updated mType, so the code in
-      // nsGenericElement::SetAttrAndNotify didn't see the new states.
-      states |= NS_EVENT_STATE_CHECKED |
-                NS_EVENT_STATE_DEFAULT |
-                NS_EVENT_STATE_BROKEN |
-                NS_EVENT_STATE_USERDISABLED |
-                NS_EVENT_STATE_SUPPRESSED |
-                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_MOZ_UI_VALID |
-                NS_EVENT_STATE_MOZ_UI_INVALID |
-                NS_EVENT_STATE_INDETERMINATE |
-                NS_EVENT_STATE_MOZ_PLACEHOLDER |
-                NS_EVENT_STATE_MOZ_SUBMITINVALID;
     }
 
     if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
       nsIRadioGroupContainer* c = GetRadioGroupContainer();
       nsCOMPtr<nsIRadioGroupContainer_MOZILLA_2_0_BRANCH> container =
         do_QueryInterface(c);
 
       if (container) {
@@ -924,44 +904,24 @@ nsHTMLInputElement::AfterSetAttr(PRInt32
     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 |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     } else if (MaxLengthApplies() && aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     } else if (aName == nsGkAtoms::pattern) {
       UpdatePatternMismatchValidityState();
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     }
 
-    if (aNotify) {
-      nsIDocument* doc = GetCurrentDoc();
-
-      if (aName == nsGkAtoms::type) {
-        UpdateEditableState();
-      } else if (IsSingleLineTextControl(PR_FALSE) && aName == nsGkAtoms::readonly) {
-        UpdateEditableState();
-        states |= NS_EVENT_STATE_MOZ_READONLY | NS_EVENT_STATE_MOZ_READWRITE;
-      }
-
-      if (doc && !states.IsEmpty()) {
-        doc->ContentStateChanged(this, states);
-      }
-    }
+    UpdateEditableState(aNotify);
+    UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
                                                 aValue, aNotify);
 }
 
 // nsIDOMHTMLInputElement
 
@@ -1018,22 +978,17 @@ nsHTMLInputElement::SetIndeterminateInte
 
   if (aShouldInvalidate) {
     // Repaint the frame
     nsIFrame* frame = GetPrimaryFrame();
     if (frame)
       frame->InvalidateFrameSubtree();
   }
 
-  // Notify the document so it can update :indeterminate pseudoclass rules
-  nsIDocument* document = GetCurrentDoc();
-  if (document) {
-    nsAutoScriptBlocker scriptBlocker;
-    document->ContentStateChanged(this, NS_EVENT_STATE_INDETERMINATE);
-  }
+  UpdateState(true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetIndeterminate(PRBool aValue)
 {
   return SetIndeterminateInternal(aValue, PR_TRUE);
@@ -1436,21 +1391,17 @@ nsHTMLInputElement::SetValueInternal(con
 
     if (aSetValueChanged) {
       SetValueChanged(PR_TRUE);
     }
     mInputData.mState->SetValue(value, aUserInput);
 
     if (PlaceholderApplies() &&
         HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
-      nsIDocument* doc = GetCurrentDoc();
-      if (doc) {
-        nsAutoScriptBlocker scriptBlocker;
-        doc->ContentStateChanged(this, NS_EVENT_STATE_MOZ_PLACEHOLDER);
-      }
+      UpdateState(true);
     }
 
     return NS_OK;
   }
 
   // If the value of a hidden input was changed, we mark it changed so that we
   // will know we need to save / restore the value.  Yes, we are overloading
   // the meaning of ValueChanged just a teensy bit to save a measly byte of
@@ -1475,22 +1426,17 @@ nsHTMLInputElement::SetValueChanged(PRBo
 
   if (!aValueChanged) {
     if (!IsSingleLineTextControl(PR_FALSE)) {
       FreeData();
     }
   }
 
   if (valueChangedBefore != aValueChanged) {
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, NS_EVENT_STATE_MOZ_UI_VALID |
-                                     NS_EVENT_STATE_MOZ_UI_INVALID);
-    }
+    UpdateState(true);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsHTMLInputElement::GetChecked(PRBool* aChecked)
 {
@@ -1524,23 +1470,17 @@ nsHTMLInputElement::SetCheckedChangedInt
 {
   PRBool checkedChangedBefore = GetCheckedChanged();
 
   SET_BOOLBIT(mBitField, BF_CHECKED_CHANGED, aCheckedChanged);
 
   // This method can't be called when we are not authorized to notify
   // so we do not need a aNotify parameter.
   if (checkedChangedBefore != aCheckedChanged) {
-    nsIDocument* document = GetCurrentDoc();
-    if (document) {
-      nsAutoScriptBlocker scriptBlocker;
-      document->ContentStateChanged(this,
-                                    NS_EVENT_STATE_MOZ_UI_VALID |
-                                    NS_EVENT_STATE_MOZ_UI_INVALID);
-    }
+    UpdateState(true);
   }
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetChecked(PRBool aChecked)
 {
   return DoSetChecked(aChecked, PR_TRUE, PR_TRUE);
 }
@@ -1724,27 +1664,21 @@ nsHTMLInputElement::SetCheckedInternal(P
   //
   if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
     nsIFrame* frame = GetPrimaryFrame();
     if (frame) {
       frame->InvalidateFrameSubtree();
     }
   }
 
+  UpdateAllValidityStates(aNotify);
+
   // Notify the document that the CSS :checked pseudoclass for this element
   // has changed state.
-  if (aNotify) {
-    nsIDocument* document = GetCurrentDoc();
-    if (document) {
-      nsAutoScriptBlocker scriptBlocker;
-      document->ContentStateChanged(this, NS_EVENT_STATE_CHECKED);
-    }
-  }
-
-  UpdateAllValidityStates(aNotify);
+  UpdateState(aNotify);
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::Focus()
 {
   if (mType == NS_FORM_INPUT_FILE) {
     // for file inputs, focus the button instead
     nsIFrame* frame = GetPrimaryFrame();
@@ -2052,40 +1986,20 @@ nsresult
 nsHTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
 {
   if (!aVisitor.mPresContext) {
     return NS_OK;
   }
 
   if (aVisitor.mEvent->message == NS_FOCUS_CONTENT ||
       aVisitor.mEvent->message == NS_BLUR_CONTENT) {
-    nsEventStates states;
-
-    if (aVisitor.mEvent->message == NS_FOCUS_CONTENT) {
-      UpdateValidityUIBits(true);
-
-      // 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 { // NS_BLUR_CONTENT
-      UpdateValidityUIBits(false);
-      states |= NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
-    }
-
-    if (PlaceholderApplies() &&
-        HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
-        // TODO: checking if the value is empty could be a good idea but we do not
-      // have a simple way to do that, see bug 585100
-      states |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
-    }
-
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, states);
-    }
+
+    UpdateValidityUIBits(aVisitor.mEvent->message == NS_FOCUS_CONTENT);
+
+    UpdateState(true);
   }
 
   // ignore the activate event fired by the "Browse..." button
   // (file input controls fire their own) (bug 500885)
   if (mType == NS_FORM_INPUT_FILE) {
     nsCOMPtr<nsIContent> maybeButton =
       do_QueryInterface(aVisitor.mEvent->originalTarget);
     if (maybeButton &&
@@ -2450,17 +2364,20 @@ nsHTMLInputElement::BindToTree(nsIDocume
                                                      aBindingParent,
                                                      aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mType == NS_FORM_INPUT_IMAGE) {
     // Our base URI may have changed; claim that our URI changed, and the
     // nsImageLoadingContent will decide whether a new image load is warranted.
     if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+      // FIXME: Bug 660963 it would be nice if we could just have
+      // ClearBrokenState update our state and do it fast...
       ClearBrokenState();
+      RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
       nsContentUtils::AddScriptRunner(
         NS_NewRunnableMethod(this, &nsHTMLInputElement::MaybeLoadImage));
     }
   }
 
   // Add radio to document if we don't have a form already (if we do it's
   // already been added into that group)
   if (aDocument && !mForm && mType == NS_FORM_INPUT_RADIO) {
@@ -2471,16 +2388,19 @@ nsHTMLInputElement::BindToTree(nsIDocume
   // We have to check if we suffer from that as we are now in a document.
   UpdateValueMissingValidityState();
 
   // If there is a disabled fieldset in the parent chain, the element is now
   // barred from constraint validation and can't suffer from value missing
   // (call done before).
   UpdateBarredFromConstraintValidation();
 
+  // And now make sure our state is up to date
+  UpdateState(false);
+
   return rv;
 }
 
 void
 nsHTMLInputElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
 {
   // If we have a form and are unbound from it,
   // nsGenericHTMLFormElement::UnbindFromTree() will unset the form and
@@ -2493,16 +2413,19 @@ nsHTMLInputElement::UnbindFromTree(PRBoo
 
   nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
 
   // GetCurrentDoc is returning nsnull so we can update the value
   // missing validity state to reflect we are no longer into a doc.
   UpdateValueMissingValidityState();
   // We might be no longer disabled because of parent chain changed.
   UpdateBarredFromConstraintValidation();
+
+  // And now make sure our state is up to date
+  UpdateState(false);
 }
 
 void
 nsHTMLInputElement::HandleTypeChange(PRUint8 aNewType)
 {
   ValueModeType aOldValueMode = GetValueMode();
   nsAutoString aOldValue;
 
@@ -3649,24 +3572,17 @@ nsHTMLInputElement::DoesPatternApply() c
 
 // nsIConstraintValidation
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetCustomValidity(const nsAString& aError)
 {
   nsIConstraintValidation::SetCustomValidity(aError);
 
-  nsIDocument* doc = GetCurrentDoc();
-  if (doc) {
-    nsAutoScriptBlocker scriptBlocker;
-    doc->ContentStateChanged(this, NS_EVENT_STATE_INVALID |
-                                   NS_EVENT_STATE_VALID |
-                                   NS_EVENT_STATE_MOZ_UI_INVALID |
-                                   NS_EVENT_STATE_MOZ_UI_VALID);
-  }
+  UpdateState(true);
 
   return NS_OK;
 }
 
 PRBool
 nsHTMLInputElement::IsTooLong()
 {
   if (!MaxLengthApplies() ||
@@ -3867,25 +3783,18 @@ void
 nsHTMLInputElement::UpdateAllValidityStates(PRBool aNotify)
 {
   PRBool validBefore = IsValid();
   UpdateTooLongValidityState();
   UpdateValueMissingValidityState();
   UpdateTypeMismatchValidityState();
   UpdatePatternMismatchValidityState();
 
-  if (validBefore != IsValid() && aNotify) {
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this,
-                               NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                               NS_EVENT_STATE_MOZ_UI_VALID |
-                               NS_EVENT_STATE_MOZ_UI_INVALID);
-    }
+  if (validBefore != IsValid()) {
+    UpdateState(aNotify);
   }
 }
 
 void
 nsHTMLInputElement::UpdateBarredFromConstraintValidation()
 {
   SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
                                     mType == NS_FORM_INPUT_BUTTON ||
@@ -4168,36 +4077,30 @@ nsHTMLInputElement::InitializeKeyboardEv
 
 NS_IMETHODIMP_(void)
 nsHTMLInputElement::OnValueChanged(PRBool aNotify)
 {
   UpdateAllValidityStates(aNotify);
 
   // :-moz-placeholder pseudo-class may change when the value changes.
   // However, we don't want to waste cycles if the state doesn't apply.
-  if (aNotify && PlaceholderApplies()
+  if (PlaceholderApplies()
       && HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)
       && !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, NS_EVENT_STATE_MOZ_PLACEHOLDER);
-    }
+    UpdateState(aNotify);
   }
 }
 
 void
-nsHTMLInputElement::FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify)
+nsHTMLInputElement::FieldSetDisabledChanged(PRBool aNotify)
 {
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
 
-  aStates |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-             NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
-  nsGenericHTMLFormElement::FieldSetDisabledChanged(aStates, aNotify);
+  nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
 }
 
 PRInt32
 nsHTMLInputElement::GetFilterFromAccept()
 {
   NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
                "You should not call GetFileFiltersFromAccept if the element"
                " has no accept attribute!");
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -156,17 +156,17 @@ public:
   // Overriden nsIFormControl methods
   NS_IMETHOD_(PRUint32) GetType() const { return mType; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   virtual PRBool RestoreState(nsPresState* aState);
   virtual PRBool AllowDrop();
 
-  virtual void FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify);
+  virtual void FieldSetDisabledChanged(PRBool aNotify);
 
   // nsIContent
   virtual PRBool IsHTMLFocusable(PRBool aWithMouse, PRBool *aIsFocusable, PRInt32 *aTabIndex);
 
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
@@ -234,19 +234,19 @@ public:
    * @return the selected button (or null).
    */
   already_AddRefed<nsIDOMHTMLInputElement> GetSelectedRadioButton();
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   NS_IMETHOD FireAsyncClickHandler();
 
-  virtual void UpdateEditableState()
+  virtual void UpdateEditableState(PRBool aNotify)
   {
-    return UpdateEditableFormControlState();
+    return UpdateEditableFormControlState(aNotify);
   }
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLInputElement,
                                                      nsGenericHTMLFormElement)
 
   static UploadLastDir* gUploadLastDir;
   // create and destroy the static UploadLastDir object for remembering
   // which directory was last used on a site-by-site basis
--- a/content/html/content/src/nsHTMLObjectElement.cpp
+++ b/content/html/content/src/nsHTMLObjectElement.cpp
@@ -158,16 +158,19 @@ nsHTMLObjectElement::nsHTMLObjectElement
   : nsGenericHTMLFormElement(aNodeInfo),
     mIsDoneAddingChildren(!aFromParser)
 {
   RegisterFreezableElement();
   SetIsNetworkCreated(aFromParser == FROM_PARSER_NETWORK);
 
   // <object> is always barred from constraint validation.
   SetBarredFromConstraintValidation(PR_TRUE);
+
+  // By default we're in the loading state
+  AddStatesSilently(NS_EVENT_STATE_LOADING);
 }
 
 nsHTMLObjectElement::~nsHTMLObjectElement()
 {
   UnregisterFreezableElement();
   DestroyImageLoadingContent();
 }
 
--- a/content/html/content/src/nsHTMLOptGroupElement.cpp
+++ b/content/html/content/src/nsHTMLOptGroupElement.cpp
@@ -96,16 +96,18 @@ protected:
 
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(OptGroup)
 
 
 nsHTMLOptGroupElement::nsHTMLOptGroupElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
 {
+  // We start off enabled
+  AddStatesSilently(NS_EVENT_STATE_ENABLED);
 }
 
 nsHTMLOptGroupElement::~nsHTMLOptGroupElement()
 {
 }
 
 
 NS_IMPL_ADDREF_INHERITED(nsHTMLOptGroupElement, nsGenericElement)
--- a/content/html/content/src/nsHTMLOptionElement.cpp
+++ b/content/html/content/src/nsHTMLOptionElement.cpp
@@ -115,16 +115,18 @@ NS_NewHTMLOptionElement(already_AddRefed
 }
 
 nsHTMLOptionElement::nsHTMLOptionElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
     mSelectedChanged(PR_FALSE),
     mIsSelected(PR_FALSE),
     mIsInSetDefaultSelected(PR_FALSE)
 {
+  // We start off enabled
+  AddStatesSilently(NS_EVENT_STATE_ENABLED);
 }
 
 nsHTMLOptionElement::~nsHTMLOptionElement()
 {
 }
 
 // ISupports
 
@@ -164,24 +166,20 @@ nsHTMLOptionElement::GetForm(nsIDOMHTMLF
 }
 
 void
 nsHTMLOptionElement::SetSelectedInternal(PRBool aValue, PRBool aNotify)
 {
   mSelectedChanged = PR_TRUE;
   mIsSelected = aValue;
 
-  // When mIsInSetDefaultSelected is true, the notification will be handled by
+  // When mIsInSetDefaultSelected is true, the state change will be handled by
   // SetAttr/UnsetAttr.
-  if (aNotify && !mIsInSetDefaultSelected) {
-    nsIDocument* document = GetCurrentDoc();
-    if (document) {
-      nsAutoScriptBlocker scriptBlocker;
-      document->ContentStateChanged(this, NS_EVENT_STATE_CHECKED);
-    }
+  if (!mIsInSetDefaultSelected) {
+    UpdateState(aNotify);
   }
 }
 
 NS_IMETHODIMP 
 nsHTMLOptionElement::GetSelected(PRBool* aValue)
 {
   NS_ENSURE_ARG_POINTER(aValue);
   *aValue = PR_FALSE;
--- a/content/html/content/src/nsHTMLOutputElement.cpp
+++ b/content/html/content/src/nsHTMLOutputElement.cpp
@@ -81,16 +81,20 @@ public:
 
   nsresult Clone(nsINodeInfo* aNodeInfo, nsINode** aResult) const;
 
   PRBool ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
                         const nsAString& aValue, nsAttrValue& aResult);
 
   nsEventStates IntrinsicState() const;
 
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                               nsIContent* aBindingParent,
+                               PRBool aCompileEventHandlers);
+
   // 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
@@ -115,16 +119,19 @@ protected:
 NS_IMPL_NS_NEW_HTML_ELEMENT(Output)
 
 
 nsHTMLOutputElement::nsHTMLOutputElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLFormElement(aNodeInfo)
   , mValueModeFlag(eModeDefault)
 {
   AddMutationObserver(this);
+
+  // We start out valid and ui-valid (since we have no form).
+  AddStatesSilently(NS_EVENT_STATE_VALID | NS_EVENT_STATE_MOZ_UI_VALID);
 }
 
 nsHTMLOutputElement::~nsHTMLOutputElement()
 {
   if (mTokenList) {
     mTokenList->DropReference();
   }
 }
@@ -152,24 +159,17 @@ NS_IMPL_STRING_ATTR(nsHTMLOutputElement,
 // nsIConstraintValidation
 NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(nsHTMLOutputElement)
 
 NS_IMETHODIMP
 nsHTMLOutputElement::SetCustomValidity(const nsAString& aError)
 {
   nsIConstraintValidation::SetCustomValidity(aError);
 
-  nsIDocument* doc = GetCurrentDoc();
-  if (doc) {
-    nsAutoScriptBlocker scriptBlocker;
-    doc->ContentStateChanged(this, NS_EVENT_STATE_INVALID |
-                                   NS_EVENT_STATE_VALID |
-                                   NS_EVENT_STATE_MOZ_UI_INVALID |
-                                   NS_EVENT_STATE_MOZ_UI_VALID);
-  }
+  UpdateState(true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLOutputElement::Reset()
 {
   mValueModeFlag = eModeDefault;
@@ -217,16 +217,36 @@ nsHTMLOutputElement::IntrinsicState() co
     if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
       states |= NS_EVENT_STATE_MOZ_UI_INVALID;
     }
   }
 
   return states;
 }
 
+nsresult
+nsHTMLOutputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                                nsIContent* aBindingParent,
+                                PRBool aCompileEventHandlers)
+{
+  nsresult rv = nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
+                                                     aBindingParent,
+                                                     aCompileEventHandlers);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Unfortunately, we can actually end up having to change our state
+  // as a result of being bound to a tree even from the parser: we
+  // might end up a in a novalidate form, and unlike other form
+  // controls that on its own is enough to make change ui-valid state.
+  // So just go ahead and update our state now.
+  UpdateState(false);
+
+  return rv;
+}
+
 NS_IMETHODIMP
 nsHTMLOutputElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElement::GetForm(aForm);
 }
 
 NS_IMETHODIMP
 nsHTMLOutputElement::GetType(nsAString& aType)
--- a/content/html/content/src/nsHTMLProgressElement.cpp
+++ b/content/html/content/src/nsHTMLProgressElement.cpp
@@ -97,16 +97,18 @@ const double nsHTMLProgressElement::kDef
 const double nsHTMLProgressElement::kDefaultMax            =  1.0;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Progress)
 
 
 nsHTMLProgressElement::nsHTMLProgressElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLFormElement(aNodeInfo)
 {
+  // We start out indeterminate
+  AddStatesSilently(NS_EVENT_STATE_INDETERMINATE);
 }
 
 nsHTMLProgressElement::~nsHTMLProgressElement()
 {
 }
 
 NS_IMPL_ADDREF_INHERITED(nsHTMLProgressElement, nsGenericElement)
 NS_IMPL_RELEASE_INHERITED(nsHTMLProgressElement, nsGenericElement)
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -151,16 +151,21 @@ nsHTMLSelectElement::nsHTMLSelectElement
     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,
   // otherwise it is
+
+  // Set up our default state: enabled, optional, and valid.
+  AddStatesSilently(NS_EVENT_STATE_ENABLED |
+                    NS_EVENT_STATE_OPTIONAL |
+                    NS_EVENT_STATE_VALID);
 }
 
 nsHTMLSelectElement::~nsHTMLSelectElement()
 {
   if (mOptions) {
     mOptions->DropReference();
   }
 }
@@ -198,24 +203,17 @@ NS_IMPL_ELEMENT_CLONE(nsHTMLSelectElemen
 // nsIConstraintValidation
 NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(nsHTMLSelectElement)
 
 NS_IMETHODIMP
 nsHTMLSelectElement::SetCustomValidity(const nsAString& aError)
 {
   nsIConstraintValidation::SetCustomValidity(aError);
 
-  nsIDocument* doc = GetCurrentDoc();
-  if (doc) {
-    nsAutoScriptBlocker scriptBlocker;
-    doc->ContentStateChanged(this, NS_EVENT_STATE_INVALID |
-                                   NS_EVENT_STATE_VALID |
-                                   NS_EVENT_STATE_MOZ_UI_INVALID |
-                                   NS_EVENT_STATE_MOZ_UI_VALID);
-  }
+  UpdateState(true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElement::GetForm(aForm);
@@ -350,26 +348,17 @@ nsHTMLSelectElement::RemoveOptionsFromLi
 
     // Select something in case we removed the selected option on a
     // single select
     if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) {
       // Update the validity state in case of we've just removed the last
       // option.
       UpdateValueMissingValidityState();
 
-      if (aNotify) {
-        nsIDocument* doc = GetCurrentDoc();
-        if (doc) {
-          nsAutoScriptBlocker scriptBlocker;
-          doc->ContentStateChanged(this, NS_EVENT_STATE_VALID |
-                                         NS_EVENT_STATE_INVALID |
-                                         NS_EVENT_STATE_MOZ_UI_INVALID |
-                                         NS_EVENT_STATE_MOZ_UI_VALID);
-        }
-      }
+      UpdateState(aNotify);
     }
   }
 
   return NS_OK;
 }
 
 static PRBool IsOptGroup(nsIContent *aContent)
 {
@@ -883,26 +872,17 @@ nsHTMLSelectElement::OnOptionSelected(ns
   }
 
   // Let the frame know too
   if (aSelectFrame) {
     aSelectFrame->OnOptionSelected(aIndex, aSelected);
   }
 
   UpdateValueMissingValidityState();
-  if (aNotify) {
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, NS_EVENT_STATE_VALID |
-                                     NS_EVENT_STATE_INVALID |
-                                     NS_EVENT_STATE_MOZ_UI_INVALID |
-                                     NS_EVENT_STATE_MOZ_UI_VALID);
-    }
-  }
+  UpdateState(aNotify);
 }
 
 void
 nsHTMLSelectElement::FindSelectedIndex(PRInt32 aStartIndex, PRBool aNotify)
 {
   mSelectedIndex = -1;
   SetSelectionChanged(PR_TRUE, aNotify);
   PRUint32 len;
@@ -1316,26 +1296,17 @@ nsHTMLSelectElement::SelectSomething(PRB
     PRBool disabled;
     nsresult rv = IsOptionDisabled(i, &disabled);
 
     if (NS_FAILED(rv) || !disabled) {
       rv = SetSelectedIndexInternal(i, aNotify);
       NS_ENSURE_SUCCESS(rv, PR_FALSE);
 
       UpdateValueMissingValidityState();
-      if (aNotify) {
-        nsIDocument* doc = GetCurrentDoc();
-        if (doc) {
-          nsAutoScriptBlocker scriptBlocker;
-          doc->ContentStateChanged(this, NS_EVENT_STATE_VALID |
-                                         NS_EVENT_STATE_INVALID |
-                                         NS_EVENT_STATE_MOZ_UI_INVALID |
-                                         NS_EVENT_STATE_MOZ_UI_VALID);
-        }
-      }
+      UpdateState(aNotify);
 
       return PR_TRUE;
     }
   }
 
   return PR_FALSE;
 }
 
@@ -1346,28 +1317,38 @@ nsHTMLSelectElement::BindToTree(nsIDocum
 {
   nsresult rv = nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
                                                      aBindingParent,
                                                      aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If there is a disabled fieldset in the parent chain, the element is now
   // barred from constraint validation.
+  // XXXbz is this still needed now that fieldset changes always call
+  // FieldSetDisabledChanged?
   UpdateBarredFromConstraintValidation();
 
+  // And now make sure our state is up to date
+  UpdateState(false);
+
   return rv;
 }
 
 void
 nsHTMLSelectElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
 {
   nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
 
   // We might be no longer disabled because our parent chain changed.
+  // XXXbz is this still needed now that fieldset changes always call
+  // FieldSetDisabledChanged?
   UpdateBarredFromConstraintValidation();
+
+  // And now make sure our state is up to date
+  UpdateState(false);
 }
 
 nsresult
 nsHTMLSelectElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                    const nsAString* aValue, PRBool aNotify)
 {
   if (aNotify && aName == nsGkAtoms::disabled &&
       aNameSpaceID == kNameSpaceID_None) {
@@ -1377,36 +1358,24 @@ nsHTMLSelectElement::BeforeSetAttr(PRInt
   return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
                                                  aValue, aNotify);
 }
 
 nsresult
 nsHTMLSelectElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                   const nsAString* aValue, PRBool aNotify)
 {
-  nsEventStates states;
-
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     } else if (aName == nsGkAtoms::required) {
       UpdateValueMissingValidityState();
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     }
-  }
 
-  if (aNotify && !states.IsEmpty()) {
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, states);
-    }
+    UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
                                                 aValue, aNotify);
 }
 
 nsresult
 nsHTMLSelectElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
@@ -1465,16 +1434,19 @@ nsHTMLSelectElement::DoneAddingChildren(
 
   // Now that we're done, select something (if it's a single select something
   // must be selected)
   if (!CheckSelectSomething(PR_FALSE)) {
     // If an option has @selected set, it will be selected during parsing but
     // with an empty value. We have to make sure the select element updates it's
     // validity state to take this into account.
     UpdateValueMissingValidityState();
+
+    // And now make sure we update our content state too
+    UpdateState(aHaveNotified);
   }
 
   mDefaultSelectionSet = PR_TRUE;
 
   return NS_OK;
 }
 
 PRBool
@@ -1568,22 +1540,17 @@ nsHTMLSelectElement::PostHandleEvent(nsE
     mCanShowValidUI = ShouldShowValidityUI();
 
     // 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) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, NS_EVENT_STATE_MOZ_UI_VALID |
-                                     NS_EVENT_STATE_MOZ_UI_INVALID);
-    }
+    UpdateState(true);
   }
 
   return nsGenericHTMLFormElement::PostHandleEvent(aVisitor);
 }
 
 nsEventStates
 nsHTMLSelectElement::IntrinsicState() const
 {
@@ -2282,37 +2249,30 @@ nsHTMLOptionCollection::Remove(PRInt32 a
 
 void
 nsHTMLSelectElement::UpdateBarredFromConstraintValidation()
 {
   SetBarredFromConstraintValidation(IsDisabled());
 }
 
 void
-nsHTMLSelectElement::FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify)
+nsHTMLSelectElement::FieldSetDisabledChanged(PRBool aNotify)
 {
   UpdateBarredFromConstraintValidation();
 
-  aStates |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-             NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
-  nsGenericHTMLFormElement::FieldSetDisabledChanged(aStates, aNotify);
+  nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
 }
 
 void
 nsHTMLSelectElement::SetSelectionChanged(PRBool aValue, PRBool aNotify)
 {
   if (!mDefaultSelectionSet) {
     return;
   }
 
   PRBool previousSelectionChangedValue = mSelectionHasChanged;
   mSelectionHasChanged = aValue;
 
-  if (aNotify && mSelectionHasChanged != previousSelectionChangedValue) {
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, NS_EVENT_STATE_MOZ_UI_INVALID |
-                                     NS_EVENT_STATE_MOZ_UI_VALID);
-    }
+  if (mSelectionHasChanged != previousSelectionChangedValue) {
+    UpdateState(aNotify);
   }
 }
 
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -284,17 +284,17 @@ public:
 
   // Overriden nsIFormControl methods
   NS_IMETHOD_(PRUint32) GetType() const { return NS_FORM_SELECT; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   virtual PRBool RestoreState(nsPresState* aState);
 
-  virtual void FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify);
+  virtual void FieldSetDisabledChanged(PRBool aNotify);
 
   nsEventStates IntrinsicState() const;
 
   /**
    * To be called when stuff is added under a child of the select--but *before*
    * they are actually added.
    *
    * @param aOptions the content that was added (usually just an option, but
--- a/content/html/content/src/nsHTMLSharedObjectElement.cpp
+++ b/content/html/content/src/nsHTMLSharedObjectElement.cpp
@@ -175,16 +175,19 @@ NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER
 
 nsHTMLSharedObjectElement::nsHTMLSharedObjectElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                                                      FromParser aFromParser)
   : nsGenericHTMLElement(aNodeInfo),
     mIsDoneAddingChildren(mNodeInfo->Equals(nsGkAtoms::embed) || !aFromParser)
 {
   RegisterFreezableElement();
   SetIsNetworkCreated(aFromParser == FROM_PARSER_NETWORK);
+
+  // By default we're in the loading state
+  AddStatesSilently(NS_EVENT_STATE_LOADING);
 }
 
 nsHTMLSharedObjectElement::~nsHTMLSharedObjectElement()
 {
   UnregisterFreezableElement();
   DestroyImageLoadingContent();
 }
 
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -129,17 +129,17 @@ public:
 
   // nsIFormControl
   NS_IMETHOD_(PRUint32) GetType() const { return NS_FORM_TEXTAREA; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
   virtual PRBool RestoreState(nsPresState* aState);
 
-  virtual void FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify);
+  virtual void FieldSetDisabledChanged(PRBool aNotify);
 
   virtual nsEventStates IntrinsicState() const;
 
   // nsITextControlElemet
   NS_IMETHOD SetValueChanged(PRBool aValueChanged);
   NS_IMETHOD_(PRBool) IsSingleLineTextControl() const;
   NS_IMETHOD_(PRBool) IsTextArea() const;
   NS_IMETHOD_(PRBool) IsPlainTextControl() const;
@@ -199,19 +199,19 @@ public:
                                  const nsAString* aValue, PRBool aNotify);
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
-  virtual void UpdateEditableState()
+  virtual void UpdateEditableState(PRBool aNotify)
   {
-    return UpdateEditableFormControlState();
+    return UpdateEditableFormControlState(aNotify);
   }
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLTextAreaElement,
                                            nsGenericHTMLFormElement)
 
   virtual nsXPCClassInfo* GetClassInfo();
 
   // nsIConstraintValidation
@@ -316,16 +316,25 @@ nsHTMLTextAreaElement::nsHTMLTextAreaEle
     mDoneAddingChildren(!aFromParser),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mDisabledChanged(PR_FALSE),
     mCanShowInvalidUI(PR_TRUE),
     mCanShowValidUI(PR_TRUE),
     mState(new nsTextEditorState(this))
 {
   AddMutationObserver(this);
+
+  // Set up our default state.  By default we're enabled (since we're
+  // a control type that can be disabled but not actually disabled
+  // right now), optional, and valid.  We are NOT readwrite by default
+  // until someone calls UpdateEditableState on us, apparently!  Also
+  // by default we don't have to show validity UI and so forth.
+  AddStatesSilently(NS_EVENT_STATE_ENABLED |
+                    NS_EVENT_STATE_OPTIONAL |
+                    NS_EVENT_STATE_VALID);
 }
 
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLTextAreaElement)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLTextAreaElement,
                                                 nsGenericHTMLFormElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mControllers)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -582,28 +591,17 @@ nsHTMLTextAreaElement::SetValueChanged(P
   PRBool previousValue = mValueChanged;
 
   mValueChanged = aValueChanged;
   if (!aValueChanged && !mState->IsEmpty()) {
     mState->EmptyValue();
   }
 
   if (mValueChanged != previousValue) {
-    nsEventStates states = NS_EVENT_STATE_MOZ_UI_VALID |
-                           NS_EVENT_STATE_MOZ_UI_INVALID;
-
-    if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
-      states |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
-    }
-
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, states);
-    }
+    UpdateState(true);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::GetDefaultValue(nsAString& aDefaultValue)
 {
@@ -742,46 +740,30 @@ nsresult
 nsHTMLTextAreaElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
 {
   if (aVisitor.mEvent->message == NS_FORM_SELECTED) {
     mHandlingSelect = PR_FALSE;
   }
 
   if (aVisitor.mEvent->message == NS_FOCUS_CONTENT ||
       aVisitor.mEvent->message == NS_BLUR_CONTENT) {
-    nsEventStates states;
-
     if (aVisitor.mEvent->message == NS_FOCUS_CONTENT) {
       // If the invalid UI is shown, we should show it while focusing (and
       // update). Otherwise, we should not.
       mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
 
       // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
       // UI while typing.
       mCanShowValidUI = ShouldShowValidityUI();
-
-      // 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 { // NS_BLUR_CONTENT
       mCanShowInvalidUI = PR_TRUE;
       mCanShowValidUI = PR_TRUE;
-      states |= NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     }
 
-    if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
-      // TODO: checking if the value is empty could be a good idea but we do not
-      // have a simple way to do that, see bug 585100
-      states |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
-    }
-
-    nsIDocument* doc = GetCurrentDoc();
-    if (doc) {
-      nsAutoScriptBlocker scriptBlocker;
-      doc->ContentStateChanged(this, states);
-    }
+    UpdateState(true);
   }
 
   // Reset the flag for other content besides this text field
   aVisitor.mEvent->flags |= (aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH)
     ? NS_EVENT_FLAG_NO_CONTENT_DISPATCH : NS_EVENT_FLAG_NONE;
 
   return NS_OK;
 }
@@ -1111,27 +1093,33 @@ nsHTMLTextAreaElement::BindToTree(nsIDoc
                                                      aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If there is a disabled fieldset in the parent chain, the element is now
   // barred from constraint validation and can't suffer from value missing.
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
 
+  // And now make sure our state is up to date
+  UpdateState(false);
+
   return rv;
 }
 
 void
 nsHTMLTextAreaElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
 {
   nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
 
   // We might be no longer disabled because of parent chain changed.
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
+
+  // And now make sure our state is up to date
+  UpdateState(false);
 }
 
 nsresult
 nsHTMLTextAreaElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                      const nsAString* aValue, PRBool aNotify)
 {
   if (aNotify && aName == nsGkAtoms::disabled &&
       aNameSpaceID == kNameSpaceID_None) {
@@ -1189,49 +1177,33 @@ nsHTMLTextAreaElement::ContentChanged(ns
     Reset();
   }
 }
 
 nsresult
 nsHTMLTextAreaElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                     const nsAString* aValue, PRBool aNotify)
 {
-  nsEventStates states;
-
   if (aNameSpaceID == kNameSpaceID_None) {
     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_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID |
-                NS_EVENT_STATE_MOZ_SUBMITINVALID;
     } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
     }
 
-    if (aNotify) {
-      nsIDocument* doc = GetCurrentDoc();
-
-      if (aName == nsGkAtoms::readonly) {
-        UpdateEditableState();
-        states |= NS_EVENT_STATE_MOZ_READONLY | NS_EVENT_STATE_MOZ_READWRITE;
-      }
-
-      if (doc && !states.IsEmpty()) {
-        doc->ContentStateChanged(this, states);
-      }
+    if (aName == nsGkAtoms::readonly) {
+      UpdateEditableState(aNotify);
     }
+    UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName, aValue,
                                                 aNotify);
 }
 
 nsresult
 nsHTMLTextAreaElement::CopyInnerTo(nsGenericElement* aDest) const
@@ -1264,24 +1236,17 @@ nsHTMLTextAreaElement::IsValueEmpty() co
 
 // nsIConstraintValidation
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetCustomValidity(const nsAString& aError)
 {
   nsIConstraintValidation::SetCustomValidity(aError);
 
-  nsIDocument* doc = GetCurrentDoc();
-  if (doc) {
-    nsAutoScriptBlocker scriptBlocker;
-    doc->ContentStateChanged(this, NS_EVENT_STATE_INVALID |
-                                   NS_EVENT_STATE_VALID |
-                                   NS_EVENT_STATE_MOZ_UI_INVALID |
-                                   NS_EVENT_STATE_MOZ_UI_VALID);
-  }
+  UpdateState(true);
 
   return NS_OK;
 }
 
 PRBool
 nsHTMLTextAreaElement::IsTooLong()
 {
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength) || !mValueChanged) {
@@ -1482,41 +1447,24 @@ nsHTMLTextAreaElement::InitializeKeyboar
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::OnValueChanged(PRBool aNotify)
 {
   // Update the validity state
   PRBool validBefore = IsValid();
   UpdateTooLongValidityState();
   UpdateValueMissingValidityState();
 
-  if (aNotify) {
-    nsEventStates states;
-    if (validBefore != IsValid()) {
-      states |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-                NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
-    }
-
-    if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)
-        && !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
-      states |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
-    }
-
-    if (!states.IsEmpty()) {
-      nsIDocument* doc = GetCurrentDoc();
-      if (doc) {
-        nsAutoScriptBlocker scriptBlocker;
-        doc->ContentStateChanged(this, states);
-      }
-    }
+  if (validBefore != IsValid() ||
+      (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)
+       && !nsContentUtils::IsFocusedContent((nsIContent*)(this)))) {
+    UpdateState(aNotify);
   }
 }
 
 void
-nsHTMLTextAreaElement::FieldSetDisabledChanged(nsEventStates aStates, PRBool aNotify)
+nsHTMLTextAreaElement::FieldSetDisabledChanged(PRBool aNotify)
 {
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
 
-  aStates |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_INVALID |
-             NS_EVENT_STATE_MOZ_UI_VALID | NS_EVENT_STATE_MOZ_UI_INVALID;
-  nsGenericHTMLFormElement::FieldSetDisabledChanged(aStates, aNotify);
+  nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
 }
 
--- a/content/html/content/src/nsRadioVisitor.cpp
+++ b/content/html/content/src/nsRadioVisitor.cpp
@@ -78,19 +78,13 @@ nsRadioSetValueMissingState::Visit(nsIFo
     return PR_TRUE;
   }
 
   nsHTMLInputElement* input = static_cast<nsHTMLInputElement*>(aRadio);
 
   input->SetValidityState(nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING,
                           mValidity);
 
-  nsIDocument* doc = input->GetCurrentDoc();
-  if (mNotify && doc) {
-    doc->ContentStateChanged(input, NS_EVENT_STATE_VALID |
-                                    NS_EVENT_STATE_INVALID |
-                                    NS_EVENT_STATE_MOZ_UI_VALID |
-                                    NS_EVENT_STATE_MOZ_UI_INVALID);
-  }
+  input->UpdateState(true);
 
   return PR_TRUE;
 }
 
--- a/content/html/content/test/test_bug345822.html
+++ b/content/html/content/test/test_bug345822.html
@@ -58,17 +58,17 @@ function checkNotSufferingFromBeingMissi
   ok(element.validity.valid, "Element should be valid");
   ok(element.checkValidity(), "Element should be valid");
   is(element.validationMessage, "",
     "Validation message should be the empty string");
 
   if (element.type != 'radio' && element.type != 'checkbox') {
     is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
        doNotApply ? "rgb(0, 0, 0)" : "rgb(0, 255, 0)",
-       "The pseudo-class is not correctly applied");
+       "The pseudo-class is not correctly applied to " + element.localName);
   }
 }
 
 function checkSufferingFromBeingMissing(element)
 {
   ok(element.validity.valueMissing, "Element should suffer from value missing");
   ok(!element.validity.valid, "Element should not be valid");
   ok(!element.checkValidity(), "Element should not be valid");
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -2727,20 +2727,19 @@ nsHTMLDocument::GetDocumentAllResult(con
 
 static void
 NotifyEditableStateChange(nsINode *aNode, nsIDocument *aDocument,
                           PRBool aEditable)
 {
   PRUint32 i, n = aNode->GetChildCount();
   for (i = 0; i < n; ++i) {
     nsIContent *child = aNode->GetChildAt(i);
-    if (child->HasFlag(NODE_IS_EDITABLE) != aEditable) {
-      aDocument->ContentStateChanged(child,
-                                     NS_EVENT_STATE_MOZ_READONLY |
-                                     NS_EVENT_STATE_MOZ_READWRITE);
+    if (child->HasFlag(NODE_IS_EDITABLE) != aEditable &&
+        child->IsElement()) {
+      child->AsElement()->UpdateState(true);
     }
     NotifyEditableStateChange(child, aDocument, aEditable);
   }
 }
 
 void
 nsHTMLDocument::TearingDownEditor(nsIEditor *aEditor)
 {
--- a/content/mathml/content/src/nsMathMLElement.cpp
+++ b/content/mathml/content/src/nsMathMLElement.cpp
@@ -458,15 +458,10 @@ nsMathMLElement::SetIncrementScriptLevel
                                          PRBool aNotify)
 {
   if (aIncrementScriptLevel == mIncrementScriptLevel)
     return;
   mIncrementScriptLevel = aIncrementScriptLevel;
 
   NS_ASSERTION(aNotify, "We always notify!");
 
-  nsIDocument* doc = GetCurrentDoc();
-  if (!doc)
-    return;
-
-  nsAutoScriptBlocker scriptBlocker;
-  doc->ContentStateChanged(this, NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL);
+  UpdateState(true);
 }
--- a/content/svg/content/src/nsSVGFilters.cpp
+++ b/content/svg/content/src/nsSVGFilters.cpp
@@ -5372,16 +5372,18 @@ NS_INTERFACE_TABLE_HEAD(nsSVGFEImageElem
 NS_INTERFACE_MAP_END_INHERITING(nsSVGFEImageElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGFEImageElement::nsSVGFEImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsSVGFEImageElementBase(aNodeInfo)
 {
+  // We start out broken
+  AddStatesSilently(NS_EVENT_STATE_BROKEN);
 }
 
 nsSVGFEImageElement::~nsSVGFEImageElement()
 {
   DestroyImageLoadingContent();
 }
 
 //----------------------------------------------------------------------
@@ -5461,17 +5463,20 @@ nsSVGFEImageElement::BindToTree(nsIDocum
                                 PRBool aCompileEventHandlers)
 {
   nsresult rv = nsSVGFEImageElementBase::BindToTree(aDocument, aParent,
                                                     aBindingParent,
                                                     aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href)) {
+    // FIXME: Bug 660963 it would be nice if we could just have
+    // ClearBrokenState update our state and do it fast...
     ClearBrokenState();
+    RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
     nsContentUtils::AddScriptRunner(
       NS_NewRunnableMethod(this, &nsSVGFEImageElement::MaybeLoadSVGImage));
   }
 
   return rv;
 }
 
 nsEventStates
--- a/content/svg/content/src/nsSVGImageElement.cpp
+++ b/content/svg/content/src/nsSVGImageElement.cpp
@@ -79,16 +79,18 @@ NS_INTERFACE_TABLE_HEAD(nsSVGImageElemen
 NS_INTERFACE_MAP_END_INHERITING(nsSVGImageElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGImageElement::nsSVGImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsSVGImageElementBase(aNodeInfo)
 {
+  // We start out broken
+  AddStatesSilently(NS_EVENT_STATE_BROKEN);
 }
 
 nsSVGImageElement::~nsSVGImageElement()
 {
   DestroyImageLoadingContent();
 }
 
 //----------------------------------------------------------------------
@@ -202,17 +204,20 @@ nsSVGImageElement::BindToTree(nsIDocumen
                               PRBool aCompileEventHandlers)
 {
   nsresult rv = nsSVGImageElementBase::BindToTree(aDocument, aParent,
                                                   aBindingParent,
                                                   aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href)) {
+    // FIXME: Bug 660963 it would be nice if we could just have
+    // ClearBrokenState update our state and do it fast...
     ClearBrokenState();
+    RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
     nsContentUtils::AddScriptRunner(
       NS_NewRunnableMethod(this, &nsSVGImageElement::MaybeLoadSVGImage));
   }
 
   return rv;
 }
 
 nsEventStates
--- a/content/xtf/src/nsXTFElementWrapper.cpp
+++ b/content/xtf/src/nsXTFElementWrapper.cpp
@@ -909,18 +909,17 @@ nsXTFElementWrapper::SetIntrinsicState(n
   if (!doc || bits.IsEmpty())
     return NS_OK;
 
   NS_WARN_IF_FALSE(!newStates.HasAllStates(NS_EVENT_STATE_MOZ_READONLY |
                                            NS_EVENT_STATE_MOZ_READWRITE),
                    "Both READONLY and READWRITE are being set.  Yikes!!!");
 
   mIntrinsicState = newStates;
-  nsAutoScriptBlocker scriptBlocker;
-  doc->ContentStateChanged(this, bits);
+  UpdateState(true);
 
   return NS_OK;
 }
 
 nsIAtom *
 nsXTFElementWrapper::GetClassAttributeName() const
 {
   return mClassAttributeName;
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -240,16 +240,22 @@ NS_INTERFACE_MAP_END_AGGREGATED(mElement
 // nsXULElement
 //
 
 nsXULElement::nsXULElement(already_AddRefed<nsINodeInfo> aNodeInfo)
     : nsStyledElement(aNodeInfo),
       mBindingParent(nsnull)
 {
     XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);
+
+    // We may be READWRITE by default; check.
+    if (IsReadWriteTextElement()) {
+        AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
+        RemoveStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
+    }
 }
 
 nsXULElement::nsXULSlots::nsXULSlots()
     : nsXULElement::nsDOMSlots()
 {
 }
 
 nsXULElement::nsXULSlots::~nsXULSlots()
@@ -887,16 +893,26 @@ nsXULElement::MaybeAddPopupListener(nsIA
         AddPopupListener(aLocalName);
     }
 }
 
 //----------------------------------------------------------------------
 //
 // nsIContent interface
 //
+void
+nsXULElement::UpdateEditableState(PRBool aNotify)
+{
+    // Don't call through to nsGenericElement here because the things
+    // it does don't work for cases when we're an editable control.
+    nsIContent *parent = GetParent();
+
+    SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
+    UpdateState(aNotify);
+}
 
 nsresult
 nsXULElement::BindToTree(nsIDocument* aDocument,
                          nsIContent* aParent,
                          nsIContent* aBindingParent,
                          PRBool aCompileEventHandlers)
 {
   nsresult rv = nsStyledElement::BindToTree(aDocument, aParent,
@@ -1357,22 +1373,17 @@ nsXULElement::UnsetAttr(PRInt32 aNameSpa
                                  "have a normal one");
 
         return NS_OK;
     }
 
     nsAutoString oldValue;
     GetAttr(aNameSpaceID, aName, oldValue);
 
-    // When notifying, make sure to keep track of states whose value
-    // depends solely on the value of an attribute.
-    nsEventStates stateMask;
     if (aNotify) {
-        stateMask = IntrinsicState();
- 
         nsNodeUtils::AttributeWillChange(this, aNameSpaceID, aName,
                                          nsIDOMMutationEvent::REMOVAL);
     }
 
     PRBool hasMutationListeners = aNotify &&
         nsContentUtils::HasMutationListeners(this,
             NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this);
 
@@ -1462,21 +1473,19 @@ nsXULElement::UnsetAttr(PRInt32 aNameSpa
     if (doc) {
         nsRefPtr<nsXBLBinding> binding =
             doc->BindingManager()->GetBinding(this);
         if (binding)
             binding->AttributeChanged(aName, aNameSpaceID, PR_TRUE, aNotify);
 
     }
 
+    UpdateState(aNotify);
+
     if (aNotify) {
-        stateMask ^= IntrinsicState();
-        if (doc && !stateMask.IsEmpty()) {
-            doc->ContentStateChanged(this, stateMask);
-        }
         nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
                                       nsIDOMMutationEvent::REMOVAL);
     }
 
     if (hasMutationListeners) {
         nsMutationEvent mutation(PR_TRUE, NS_MUTATION_ATTRMODIFIED);
 
         mutation.mRelatedNode = attrNode;
@@ -2243,20 +2252,17 @@ nsXULElement::AddPopupListener(nsIAtom* 
     return NS_OK;
 }
 
 nsEventStates
 nsXULElement::IntrinsicState() const
 {
     nsEventStates state = nsStyledElement::IntrinsicState();
 
-    const nsIAtom* tag = Tag();
-    if (GetNameSpaceID() == kNameSpaceID_XUL &&
-        (tag == nsGkAtoms::textbox || tag == nsGkAtoms::textarea) &&
-        !HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) {
+    if (IsReadWriteTextElement()) {
         state |= NS_EVENT_STATE_MOZ_READWRITE;
         state &= ~NS_EVENT_STATE_MOZ_READONLY;
     }
 
     return state;
 }
 
 //----------------------------------------------------------------------
--- a/content/xul/content/src/nsXULElement.h
+++ b/content/xul/content/src/nsXULElement.h
@@ -646,16 +646,18 @@ protected:
         return nsXULElement::GetAttrInfo(aNameSpaceID, aName).mValue;
     }
 
     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 void UpdateEditableState(PRBool aNotify);
+
     virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult);
 
     virtual nsresult
       GetEventListenerManagerForAttr(nsIEventListenerManager** aManager,
                                      nsISupports** aTarget,
@@ -703,11 +705,20 @@ protected:
     friend void
     NS_TrustedNewXULElement(nsIContent** aResult, nsINodeInfo *aNodeInfo);
 
     static already_AddRefed<nsXULElement>
     Create(nsXULPrototypeElement* aPrototype, nsINodeInfo *aNodeInfo,
            PRBool aIsScriptable);
 
     friend class nsScriptEventHandlerOwnerTearoff;
+
+    bool IsReadWriteTextElement() const
+    {
+        const nsIAtom* tag = Tag();
+        return
+            GetNameSpaceID() == kNameSpaceID_XUL &&
+            (tag == nsGkAtoms::textbox || tag == nsGkAtoms::textarea) &&
+            !HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+    }
 };
 
 #endif // nsXULElement_h__
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -61,16 +61,17 @@
 
 #ifdef ACCESSIBILITY
 #include "nsIServiceManager.h"
 #include "nsAccessibilityService.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::layers;
+using namespace mozilla::dom;
 
 nsIFrame*
 NS_NewHTMLVideoFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsVideoFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)
@@ -96,33 +97,36 @@ nsVideoFrame::CreateAnonymousContent(nsT
   if (HasVideoElement()) {
     // Create an anonymous image element as a child to hold the poster
     // image. We may not have a poster image now, but one could be added
     // before we load, or on a subsequent load.
     nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::img,
                                             nsnull,
                                             kNameSpaceID_XHTML);
     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
-    mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget());
+    Element* element = NS_NewHTMLImageElement(nodeInfo.forget());
+    mPosterImage = element;
     NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY);
 
     // Push a null JSContext on the stack so that code that runs
     // within the below code doesn't think it's being called by
     // JS. See bug 604262.
     nsCxPusher pusher;
     pusher.PushNull();
 
     // Set the nsImageLoadingContent::ImageState() to 0. This means that the
     // image will always report its state as 0, so it will never be reframed
     // to show frames for loading or the broken image icon. This is important,
     // as the image is native anonymous, and so can't be reframed (currently).
     nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage);
     NS_ENSURE_TRUE(imgContent, NS_ERROR_FAILURE);
 
     imgContent->ForceImageState(PR_TRUE, 0);
+    // And now have it update its internal state
+    element->UpdateState(false);
 
     nsresult res = UpdatePosterSource(PR_FALSE);
     NS_ENSURE_SUCCESS(res,res);
 
     if (!aElements.AppendElement(mPosterImage))
       return NS_ERROR_OUT_OF_MEMORY;
   }