Bug 418845 Enabling a11y massively degrades performance of dynamic <option> addition on Linux r=aaronleventhal, surkov.alexander sr=neil a=dsicore
authorginn.chen@sun.com
Mon, 17 Mar 2008 01:13:10 -0700
changeset 13167 3fd1d9fa068bb0f916d3a63c5be2e34dfd8e3600
parent 13166 2c777a694590d67643c02f5678f9fbebaa150092
child 13168 ca9fd7937bac1c08255a47b6f915cca6e054a8e0
push idunknown
push userunknown
push dateunknown
reviewersaaronleventhal, surkov.alexander, neil, dsicore
bugs418845
milestone1.9b5pre
Bug 418845 Enabling a11y massively degrades performance of dynamic <option> addition on Linux r=aaronleventhal, surkov.alexander sr=neil a=dsicore
accessible/src/base/nsAccessibilityUtils.cpp
accessible/src/base/nsAccessibilityUtils.h
accessible/src/base/nsAccessibleEventData.cpp
accessible/src/base/nsAccessibleEventData.h
accessible/src/base/nsCaretAccessible.cpp
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
accessible/src/base/nsRootAccessible.cpp
accessible/src/html/nsHTMLSelectAccessible.cpp
--- a/accessible/src/base/nsAccessibilityUtils.cpp
+++ b/accessible/src/base/nsAccessibilityUtils.cpp
@@ -314,31 +314,45 @@ nsAccUtils::FireAccEvent(PRUint32 aEvent
 
   return pAccessible->FireAccessibleEvent(event);
 }
 
 PRBool
 nsAccUtils::IsAncestorOf(nsIDOMNode *aPossibleAncestorNode,
                          nsIDOMNode *aPossibleDescendantNode)
 {
-  NS_ENSURE_TRUE(aPossibleAncestorNode, PR_FALSE);
-  NS_ENSURE_TRUE(aPossibleDescendantNode, PR_FALSE);
+  NS_ENSURE_TRUE(aPossibleAncestorNode && aPossibleDescendantNode, PR_FALSE);
 
   nsCOMPtr<nsIDOMNode> loopNode = aPossibleDescendantNode;
   nsCOMPtr<nsIDOMNode> parentNode;
   while (NS_SUCCEEDED(loopNode->GetParentNode(getter_AddRefs(parentNode))) &&
          parentNode) {
     if (parentNode == aPossibleAncestorNode) {
       return PR_TRUE;
     }
     loopNode.swap(parentNode);
   }
   return PR_FALSE;
 }
 
+PRBool
+nsAccUtils::AreSiblings(nsIDOMNode *aDOMNode1,
+                       nsIDOMNode *aDOMNode2)
+{
+  NS_ENSURE_TRUE(aDOMNode1 && aDOMNode2, PR_FALSE);
+
+  nsCOMPtr<nsIDOMNode> parentNode1, parentNode2;
+  if (NS_SUCCEEDED(aDOMNode1->GetParentNode(getter_AddRefs(parentNode1))) &&
+      NS_SUCCEEDED(aDOMNode2->GetParentNode(getter_AddRefs(parentNode2))) &&
+      parentNode1 == parentNode2) {
+    return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
 already_AddRefed<nsIAccessible>
 nsAccUtils::GetAncestorWithRole(nsIAccessible *aDescendant, PRUint32 aRole)
 {
   nsCOMPtr<nsIAccessible> parentAccessible = aDescendant, testRoleAccessible;
   while (NS_SUCCEEDED(parentAccessible->GetParent(getter_AddRefs(testRoleAccessible))) &&
          testRoleAccessible) {
     PRUint32 testRole;
     testRoleAccessible->GetFinalRole(&testRole);
--- a/accessible/src/base/nsAccessibilityUtils.h
+++ b/accessible/src/base/nsAccessibilityUtils.h
@@ -144,16 +144,23 @@ public:
    * @param aPossibleAncestorNode -- node to test for ancestor-ness of aPossibleDescendantNode
    * @param aPossibleDescendantNode -- node to test for descendant-ness of aPossibleAncestorNode
    * @return PR_TRUE if aPossibleAncestorNode is an ancestor of aPossibleDescendantNode
    */
    static PRBool IsAncestorOf(nsIDOMNode *aPossibleAncestorNode,
                               nsIDOMNode *aPossibleDescendantNode);
 
   /**
+   * Are the first node and the second siblings?
+   * @return PR_TRUE if aDOMNode1 and aDOMNode2 have same parent
+   */
+   static PRBool AreSiblings(nsIDOMNode *aDOMNode1,
+                             nsIDOMNode *aDOMNode2);
+
+  /**
     * If an ancestor in this document exists with the given role, return it
     * @param aDescendant Descendant to start search with
     * @param aRole Role to find matching ancestor for
     * @return The ancestor accessible with the given role, or nsnull if no match is found
     */
    static already_AddRefed<nsIAccessible>
      GetAncestorWithRole(nsIAccessible *aDescendant, PRUint32 aRole);
 
--- a/accessible/src/base/nsAccessibleEventData.cpp
+++ b/accessible/src/base/nsAccessibleEventData.cpp
@@ -52,28 +52,28 @@
 #include "nsIAccessibleText.h"
 #include "nsIContent.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 
 PRBool nsAccEvent::gLastEventFromUserInput = PR_FALSE;
 nsIDOMNode* nsAccEvent::gLastEventNodeWeak = 0;
 
-NS_IMPL_ISUPPORTS1(nsAccEvent, nsIAccessibleEvent)
+NS_IMPL_ISUPPORTS2(nsAccEvent, nsAccEvent, nsIAccessibleEvent)
 
 nsAccEvent::nsAccEvent(PRUint32 aEventType, nsIAccessible *aAccessible,
-                       PRBool aIsAsynch):
-  mEventType(aEventType), mAccessible(aAccessible)
+                       PRBool aIsAsynch, EEventRule aEventRule):
+  mEventType(aEventType), mAccessible(aAccessible), mEventRule(aEventRule)
 {
   CaptureIsFromUserInput(aIsAsynch);
 }
 
 nsAccEvent::nsAccEvent(PRUint32 aEventType, nsIDOMNode *aDOMNode,
-                       PRBool aIsAsynch):
-  mEventType(aEventType), mDOMNode(aDOMNode)
+                       PRBool aIsAsynch, EEventRule aEventRule):
+  mEventType(aEventType), mDOMNode(aDOMNode), mEventRule(aEventRule)
 {
   CaptureIsFromUserInput(aIsAsynch);
 }
 
 void nsAccEvent::GetLastEventAttributes(nsIDOMNode *aNode,
                                         nsIPersistentProperties *aAttributes)
 {
   if (aNode == gLastEventNodeWeak) {
@@ -277,16 +277,104 @@ nsAccEvent::GetAccessibleByNode()
       }
     }
   }
 #endif
 
   return accessible;
 }
 
+/* static */
+void
+nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire)
+{
+  PRUint32 numQueuedEvents = aEventsToFire.Count();
+  for (PRInt32 tail = numQueuedEvents - 1; tail >= 0; tail --) {
+    nsRefPtr<nsAccEvent> tailEvent = GetAccEventPtr(aEventsToFire[tail]);
+    switch(tailEvent->mEventRule) {
+      case nsAccEvent::eCoalesceFromSameSubtree:
+      {
+        for (PRInt32 index = 0; index < tail; index ++) {
+          nsRefPtr<nsAccEvent> thisEvent = GetAccEventPtr(aEventsToFire[index]);
+          if (thisEvent->mEventType != tailEvent->mEventType)
+            continue; // Different type
+
+          if (thisEvent->mEventRule == nsAccEvent::eAllowDupes ||
+              thisEvent->mEventRule == nsAccEvent::eDoNotEmit)
+            continue; //  Do not need to check
+
+          if (thisEvent->mDOMNode == tailEvent->mDOMNode) {
+            // Dupe
+            thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
+            continue;
+          }
+          if (nsAccUtils::IsAncestorOf(tailEvent->mDOMNode,
+                                       thisEvent->mDOMNode)) {
+            // thisDOMNode is a descendant of tailDOMNode
+            // Do not emit thisEvent, also apply this result to sibling
+            // nodes of thisDOMNode.
+            thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
+            ApplyToSiblings(aEventsToFire, 0, index, thisEvent->mEventType,
+                            thisEvent->mDOMNode, nsAccEvent::eDoNotEmit);
+            continue;
+          }
+          if (nsAccUtils::IsAncestorOf(thisEvent->mDOMNode,
+                                       tailEvent->mDOMNode)) {
+            // tailDOMNode is a descendant of thisDOMNode
+            // Do not emit tailEvent, also apply this result to sibling
+            // nodes of tailDOMNode.
+            tailEvent->mEventRule = nsAccEvent::eDoNotEmit;
+            ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
+                            tailEvent->mDOMNode, nsAccEvent::eDoNotEmit);
+            break;
+          }
+        } // for (index)
+
+        if (tailEvent->mEventRule != nsAccEvent::eDoNotEmit) {
+          // Not in another event node's subtree, and no other event is in
+          // this event node's subtree.
+          // This event should be emitted
+          // Apply this result to sibling nodes of tailDOMNode
+          ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
+                          tailEvent->mDOMNode, nsAccEvent::eAllowDupes);
+        }
+      } break; // case eCoalesceFromSameSubtree
+
+      case nsAccEvent::eRemoveDupes:
+      {
+        // Check for repeat events.
+        for (PRInt32 index = 0; index < tail; index ++) {
+          nsRefPtr<nsAccEvent> accEvent = GetAccEventPtr(aEventsToFire[index]);
+          if (accEvent->mEventType == tailEvent->mEventType &&
+              accEvent->mEventRule == tailEvent->mEventRule &&
+              accEvent->mDOMNode == tailEvent->mDOMNode) {
+            accEvent->mEventRule = nsAccEvent::eDoNotEmit;
+          }
+        }
+      } break; // case eRemoveDupes
+    } // switch
+  } // for (tail)
+}
+
+/* static */
+void
+nsAccEvent::ApplyToSiblings(nsCOMArray<nsIAccessibleEvent> &aEventsToFire,
+                            PRUint32 aStart, PRUint32 aEnd,
+                             PRUint32 aEventType, nsIDOMNode* aDOMNode,
+                             EEventRule aEventRule)
+{
+  for (PRUint32 index = aStart; index < aEnd; index ++) {
+    nsRefPtr<nsAccEvent> accEvent = GetAccEventPtr(aEventsToFire[index]);
+    if (accEvent->mEventType == aEventType &&
+        accEvent->mEventRule != nsAccEvent::eDoNotEmit &&
+        nsAccUtils::AreSiblings(accEvent->mDOMNode, aDOMNode)) {
+      accEvent->mEventRule = aEventRule;
+    }
+  }
+}
 
 // nsAccStateChangeEvent
 NS_IMPL_ISUPPORTS_INHERITED1(nsAccStateChangeEvent, nsAccEvent,
                              nsIAccessibleStateChangeEvent)
 
 nsAccStateChangeEvent::
   nsAccStateChangeEvent(nsIAccessible *aAccessible,
                         PRUint32 aState, PRBool aIsExtraState,
--- a/accessible/src/base/nsAccessibleEventData.h
+++ b/accessible/src/base/nsAccessibleEventData.h
@@ -36,56 +36,106 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _nsAccessibleEventData_H_
 #define _nsAccessibleEventData_H_
 
+#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
+#include "nsCOMArray.h"
 #include "nsIAccessibleEvent.h"
 #include "nsIAccessible.h"
 #include "nsIAccessibleDocument.h"
 #include "nsIDOMNode.h"
 #include "nsString.h"
 
 class nsIPresShell;
 
+#define NS_ACCEVENT_IMPL_CID                            \
+{  /* 55b89892-a83d-4252-ba78-cbdf53a86936 */           \
+  0x55b89892,                                           \
+  0xa83d,                                               \
+  0x4252,                                               \
+  { 0xba, 0x78, 0xcb, 0xdf, 0x53, 0xa8, 0x69, 0x36 }    \
+}
+
 class nsAccEvent: public nsIAccessibleEvent
 {
 public:
+
+  // Rule for accessible events.
+  // The rule will be applied when flushing pending events.
+  enum EEventRule {
+     // eAllowDupes : More than one event of the same type is allowed.
+     //    This event will always be emitted.
+     eAllowDupes,
+     // eCoalesceFromSameSubtree : For events of the same type from the same
+     //    subtree or the same node, only the umbrelle event on the ancestor
+     //    will be emitted.
+     eCoalesceFromSameSubtree,
+     // eRemoveDupes : For repeat events, only the newest event in queue
+     //    will be emitted.
+     eRemoveDupes,
+     // eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
+     eDoNotEmit
+   };
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCEVENT_IMPL_CID)
+
   // Initialize with an nsIAccessible
-  nsAccEvent(PRUint32 aEventType, nsIAccessible *aAccessible, PRBool aIsAsynch = PR_FALSE);
+  nsAccEvent(PRUint32 aEventType, nsIAccessible *aAccessible,
+             PRBool aIsAsynch = PR_FALSE,
+             EEventRule aEventRule = eRemoveDupes);
   // Initialize with an nsIDOMNode
-  nsAccEvent(PRUint32 aEventType, nsIDOMNode *aDOMNode, PRBool aIsAsynch = PR_FALSE);
+  nsAccEvent(PRUint32 aEventType, nsIDOMNode *aDOMNode,
+             PRBool aIsAsynch = PR_FALSE,
+             EEventRule aEventRule = eRemoveDupes);
   virtual ~nsAccEvent() {}
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIACCESSIBLEEVENT
 
   static void GetLastEventAttributes(nsIDOMNode *aNode,
                                      nsIPersistentProperties *aAttributes);
 
 protected:
   already_AddRefed<nsIAccessible> GetAccessibleByNode();
 
   void CaptureIsFromUserInput(PRBool aIsAsynch);
   PRBool mIsFromUserInput;
 
 private:
   PRUint32 mEventType;
+  EEventRule mEventRule;
   nsCOMPtr<nsIAccessible> mAccessible;
   nsCOMPtr<nsIDOMNode> mDOMNode;
   nsCOMPtr<nsIAccessibleDocument> mDocAccessible;
 
   static PRBool gLastEventFromUserInput;
   static nsIDOMNode* gLastEventNodeWeak;
 
 public:
+  static PRUint32 EventType(nsIAccessibleEvent *aAccEvent) {
+    PRUint32 eventType;
+    aAccEvent->GetEventType(&eventType);
+    return eventType;
+  }
+  static EEventRule EventRule(nsIAccessibleEvent *aAccEvent) {
+    nsRefPtr<nsAccEvent> accEvent = GetAccEventPtr(aAccEvent);
+    return accEvent->mEventRule;
+  }
+  static PRBool IsFromUserInput(nsIAccessibleEvent *aAccEvent) {
+    PRBool isFromUserInput;
+    aAccEvent->GetIsFromUserInput(&isFromUserInput);
+    return isFromUserInput;
+  }
+
   static void ResetLastInputState()
    {gLastEventFromUserInput = PR_FALSE; gLastEventNodeWeak = nsnull; }
 
   /**
    * Find and cache the last input state. This will be called automatically
    * for synchronous events. For asynchronous events it should be
    * called from the synchronous code which is the true source of the event,
    * before the event is fired.
@@ -98,18 +148,51 @@ public:
 
   /**
    * The input state was previously stored with the nsIAccessibleEvent,
    * so use that state now -- call this when about to flush an event that 
    * was waiting in an event queue
    */
   static void PrepareForEvent(nsIAccessibleEvent *aEvent,
                               PRBool aForceIsFromUserInput = PR_FALSE);
+
+  /**
+   * Apply event rules to pending events, this method is called in
+   * FlushingPendingEvents().
+   * Result of this method:
+   *   Event rule of filtered events will be set to eDoNotEmit.
+   *   Events with other event rule are good to emit.
+   */
+  static void ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire);
+
+private:
+  static already_AddRefed<nsAccEvent> GetAccEventPtr(nsIAccessibleEvent *aAccEvent) {
+    nsAccEvent* accEvent = nsnull;
+    aAccEvent->QueryInterface(NS_GET_IID(nsAccEvent), (void**)&accEvent);
+    return accEvent;
+  }
+
+  /**
+   * Apply aEventRule to same type event that from sibling nodes of aDOMNode.
+   * @param aEventsToFire    array of pending events
+   * @param aStart           start index of pending events to be scanned
+   * @param aEnd             end index to be scanned (not included)
+   * @param aEventType       target event type
+   * @param aDOMNode         target are siblings of this node
+   * @param aEventRule       the event rule to be applied
+   *                         (should be eDoNotEmit or eAllowDupes)
+   */
+  static void ApplyToSiblings(nsCOMArray<nsIAccessibleEvent> &aEventsToFire,
+                              PRUint32 aStart, PRUint32 aEnd,
+                              PRUint32 aEventType, nsIDOMNode* aDOMNode,
+                              EEventRule aEventRule);
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAccEvent, NS_ACCEVENT_IMPL_CID)
+
 class nsAccStateChangeEvent: public nsAccEvent,
                              public nsIAccessibleStateChangeEvent
 {
 public:
   nsAccStateChangeEvent(nsIAccessible *aAccessible,
                         PRUint32 aState, PRBool aIsExtraState,
                         PRBool aIsEnabled);
 
--- a/accessible/src/base/nsCaretAccessible.cpp
+++ b/accessible/src/base/nsCaretAccessible.cpp
@@ -239,17 +239,17 @@ NS_IMETHODIMP nsCaretAccessible::NotifyS
   }
   mLastCaretOffset = caretOffset;
   mLastTextAccessible = textAcc;
 
   nsCOMPtr<nsIAccessibleCaretMoveEvent> event =
     new nsAccCaretMoveEvent(focusNode);
   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 
-  return mRootAccessible->FireDelayedAccessibleEvent(event, nsDocAccessible::eRemoveDupes);
+  return mRootAccessible->FireDelayedAccessibleEvent(event);
 }
 
 nsRect
 nsCaretAccessible::GetCaretRect(nsIWidget **aOutWidget)
 {
   nsRect caretRect;
   NS_ENSURE_TRUE(aOutWidget, caretRect);
   *aOutWidget = nsnull;
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1059,17 +1059,17 @@ nsDocAccessible::AttributeChangedImpl(ns
     // in the wrong namespace or an element that doesn't support it
     InvalidateCacheSubtree(aContent, nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE);
     return;
   }
   
   if (aAttribute == nsAccessibilityAtoms::alt ||
       aAttribute == nsAccessibilityAtoms::title) {
     FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
-                            targetNode, eRemoveDupes);
+                            targetNode);
     return;
   }
 
   if (aAttribute == nsAccessibilityAtoms::selected ||
       aAttribute == nsAccessibilityAtoms::aria_selected) {
     // ARIA or XUL selection
     nsCOMPtr<nsIAccessible> multiSelect = GetMultiSelectFor(targetNode);
     // Multi selects use selection_add and selection_remove
@@ -1081,17 +1081,18 @@ nsDocAccessible::AttributeChangedImpl(ns
       // Need to find the right event to use here, SELECTION_WITHIN would
       // seem right but we had started using it for something else
       nsCOMPtr<nsIAccessNode> multiSelectAccessNode =
         do_QueryInterface(multiSelect);
       nsCOMPtr<nsIDOMNode> multiSelectDOMNode;
       multiSelectAccessNode->GetDOMNode(getter_AddRefs(multiSelectDOMNode));
       NS_ASSERTION(multiSelectDOMNode, "A new accessible without a DOM node!");
       FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
-                              multiSelectDOMNode, eAllowDupes);
+                              multiSelectDOMNode,
+                              nsAccEvent::eAllowDupes);
 
       static nsIContent::AttrValuesArray strings[] =
         {&nsAccessibilityAtoms::_empty, &nsAccessibilityAtoms::_false, nsnull};
       if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute,
                                     strings, eCaseMatters) >= 0) {
         FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
                                 targetNode);
         return;
@@ -1432,103 +1433,39 @@ nsDocAccessible::CreateTextChangeEventFo
     new nsAccTextChangeEvent(aContainerAccessible, offset, length, aIsInserting, aIsAsynch);
   NS_IF_ADDREF(event);
 
   return event;
 }
   
 nsresult nsDocAccessible::FireDelayedToolkitEvent(PRUint32 aEvent,
                                                   nsIDOMNode *aDOMNode,
-                                                  EDupeEventRule aAllowDupes,
+                                                  nsAccEvent::EEventRule aAllowDupes,
                                                   PRBool aIsAsynch)
 {
   nsCOMPtr<nsIAccessibleEvent> event =
-    new nsAccEvent(aEvent, aDOMNode, aIsAsynch);
+    new nsAccEvent(aEvent, aDOMNode, aIsAsynch, aAllowDupes);
   NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 
-  return FireDelayedAccessibleEvent(event, aAllowDupes);
+  return FireDelayedAccessibleEvent(event);
 }
 
 nsresult
-nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent,
-                                            EDupeEventRule aAllowDupes)
+nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent)
 {
   NS_ENSURE_TRUE(aEvent, NS_ERROR_FAILURE);
 
-  PRBool isTimerStarted = PR_TRUE;
-  PRInt32 numQueuedEvents = mEventsToFire.Count();
   if (!mFireEventTimer) {
     // Do not yet have a timer going for firing another event.
     mFireEventTimer = do_CreateInstance("@mozilla.org/timer;1");
     NS_ENSURE_TRUE(mFireEventTimer, NS_ERROR_OUT_OF_MEMORY);
   }
 
-  PRUint32 newEventType;
-  aEvent->GetEventType(&newEventType);
-
-  nsCOMPtr<nsIDOMNode> newEventDOMNode;
-  aEvent->GetDOMNode(getter_AddRefs(newEventDOMNode));
-
-  if (numQueuedEvents == 0) {
-    isTimerStarted = PR_FALSE;
-  } else if (aAllowDupes == eCoalesceFromSameSubtree) {
-    // Especially for mutation events, we will define a duplicate event
-    // as one on the same node or on a descendant node.
-    // This prevents a flood of events when a subtree is changed.
-    for (PRInt32 index = 0; index < numQueuedEvents; index ++) {
-      nsIAccessibleEvent *accessibleEvent = mEventsToFire[index];
-      NS_ASSERTION(accessibleEvent, "Array item is not an accessible event");
-      if (!accessibleEvent) {
-        continue;
-      }
-      PRUint32 eventType;
-      accessibleEvent->GetEventType(&eventType);
-      if (eventType == newEventType) {
-        nsCOMPtr<nsIDOMNode> domNode;
-        accessibleEvent->GetDOMNode(getter_AddRefs(domNode));
-        if (newEventDOMNode == domNode || nsAccUtils::IsAncestorOf(newEventDOMNode, domNode)) {
-          mEventsToFire.RemoveObjectAt(index);
-          // The other event is the same type, but in a descendant of this
-          // event, so remove that one. The umbrella event in the ancestor
-          // is already enough
-          -- index;
-          -- numQueuedEvents;
-        }
-        else if (nsAccUtils::IsAncestorOf(domNode, newEventDOMNode)) {
-          // There is a better SHOW/HIDE event (it's in an ancestor)
-          return NS_OK;
-        }
-      }    
-    }
-  } else if (aAllowDupes == eRemoveDupes) {
-    // Check for repeat events. If a redundant event exists remove
-    // original and put the new event at the end of the queue
-    // so it is fired after the others
-    for (PRInt32 index = 0; index < numQueuedEvents; index ++) {
-      nsIAccessibleEvent *accessibleEvent = mEventsToFire[index];
-      NS_ASSERTION(accessibleEvent, "Array item is not an accessible event");
-      if (!accessibleEvent) {
-        continue;
-      }
-      PRUint32 eventType;
-      accessibleEvent->GetEventType(&eventType);
-      if (eventType == newEventType) {
-        nsCOMPtr<nsIDOMNode> domNode;
-        accessibleEvent->GetDOMNode(getter_AddRefs(domNode));
-        if (domNode == newEventDOMNode) {
-          mEventsToFire.RemoveObjectAt(index);
-          -- index;
-          -- numQueuedEvents;
-        }
-      }
-    }
-  }
-
   mEventsToFire.AppendObject(aEvent);
-  if (!isTimerStarted) {
+  if (mEventsToFire.Count() == 1) {
     // This is be the first delayed event in queue, start timer
     // so that event gets fired via FlushEventsCallback
     NS_ADDREF_THIS(); // Kung fu death grip to prevent crash in callback
     mFireEventTimer->InitWithFuncCallback(FlushEventsCallback,
                                           static_cast<nsPIAccessibleDocument*>(this),
                                           0, nsITimer::TYPE_ONE_SHOT);
   }
 
@@ -1537,30 +1474,32 @@ nsDocAccessible::FireDelayedAccessibleEv
 
 NS_IMETHODIMP nsDocAccessible::FlushPendingEvents()
 {
   PRUint32 length = mEventsToFire.Count();
   NS_ASSERTION(length, "How did we get here without events to fire?");
   nsCOMPtr<nsIPresShell> presShell = GetPresShell();
   if (!presShell)
     length = 0; // The doc is now shut down, don't fire events in it anymore
-  PRUint32 index;
-  for (index = 0; index < length; index ++) {
+  else
+    nsAccEvent::ApplyEventRules(mEventsToFire);
+  
+  for (PRUint32 index = 0; index < length; index ++) {
     nsCOMPtr<nsIAccessibleEvent> accessibleEvent(
       do_QueryInterface(mEventsToFire[index]));
-    NS_ASSERTION(accessibleEvent, "Array item is not an accessible event");
+
+    if (nsAccEvent::EventRule(accessibleEvent) == nsAccEvent::eDoNotEmit)
+      continue;
 
     nsCOMPtr<nsIAccessible> accessible;
     accessibleEvent->GetAccessible(getter_AddRefs(accessible));
     nsCOMPtr<nsIDOMNode> domNode;
     accessibleEvent->GetDOMNode(getter_AddRefs(domNode));
-    PRUint32 eventType;
-    accessibleEvent->GetEventType(&eventType);
-    PRBool isFromUserInput;
-    accessibleEvent->GetIsFromUserInput(&isFromUserInput);
+    PRUint32 eventType = nsAccEvent::EventType(accessibleEvent);
+    PRBool isFromUserInput = nsAccEvent::IsFromUserInput(accessibleEvent);
 
     if (domNode == gLastFocusedNode &&
         eventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE || 
         eventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW) {
       // If frame type didn't change for this event, then we don't actually need to invalidate
       // However, we only keep track of the old frame type for the focus, where it's very
       // important not to destroy and recreate the accessible for minor style changes,
       // such as a:focus { overflow: scroll; }
@@ -1973,33 +1912,35 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
     // Fire EVENT_SHOW, EVENT_MENUPOPUP_START for newly visible content.
     // Fire after a short timer, because we want to make sure the view has been
     // updated to make this accessible content visible. If we don't wait,
     // the assistive technology may receive the event and then retrieve
     // nsIAccessibleStates::STATE_INVISIBLE for the event's accessible object.
     PRUint32 additionEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_SHOW :
                                         nsIAccessibleEvent::EVENT_DOM_CREATE;
     FireDelayedToolkitEvent(additionEvent, childNode,
-                            eCoalesceFromSameSubtree, isAsynch);
+                            nsAccEvent::eCoalesceFromSameSubtree,
+                            isAsynch);
 
     // Check to see change occured in an ARIA menu, and fire
     // an EVENT_MENUPOPUP_START if it did.
     nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(childNode);
     if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_MENUPOPUP) {
       FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
-                              childNode, eRemoveDupes, isAsynch);
+                              childNode, nsAccEvent::eRemoveDupes,
+                              isAsynch);
     }
 
     // Check to see if change occured inside an alert, and fire an EVENT_ALERT if it did
     nsIContent *ancestor = aChild;
     while (PR_TRUE) {
       if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) {
         nsCOMPtr<nsIDOMNode> alertNode(do_QueryInterface(ancestor));
         FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode,
-                                eRemoveDupes, isAsynch);
+                                nsAccEvent::eRemoveDupes, isAsynch);
         break;
       }
       ancestor = ancestor->GetParent();
       nsCOMPtr<nsIDOMNode> ancestorNode = do_QueryInterface(ancestor);
       if (!ancestorNode) {
         break;
       }
       roleMapEntry = nsAccUtils::GetRoleMapEntry(ancestorNode);
@@ -2007,19 +1948,20 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
   }
 
   if (!isShowing) {
     // Fire an event so the assistive technology knows the children have changed
     // This is only used by older MSAA clients. Newer ones should derive this
     // from SHOW and HIDE so that they don't fetch extra objects
     if (childAccessible) {
       nsCOMPtr<nsIAccessibleEvent> reorderEvent =
-        new nsAccEvent(nsIAccessibleEvent::EVENT_REORDER, containerAccessible, isAsynch);
+        new nsAccEvent(nsIAccessibleEvent::EVENT_REORDER, containerAccessible,
+                       isAsynch, nsAccEvent::eCoalesceFromSameSubtree);
       NS_ENSURE_TRUE(reorderEvent, NS_ERROR_OUT_OF_MEMORY);
-      FireDelayedAccessibleEvent(reorderEvent, eCoalesceFromSameSubtree);
+      FireDelayedAccessibleEvent(reorderEvent);
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocAccessible::GetAccessibleInParentChain(nsIDOMNode *aNode,
@@ -2085,23 +2027,24 @@ nsDocAccessible::FireShowHideEvents(nsID
 
   if (accessible) {
     // Found an accessible, so fire the show/hide on it and don't
     // look further into this subtree
     PRBool isAsynch = aEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
                       aEventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW;
 
     nsCOMPtr<nsIAccessibleEvent> event =
-      new nsAccEvent(aEventType, accessible, isAsynch);
+      new nsAccEvent(aEventType, accessible, isAsynch,
+                     nsAccEvent::eCoalesceFromSameSubtree);
     NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
     if (aForceIsFromUserInput) {
       nsAccEvent::PrepareForEvent(event, aForceIsFromUserInput);
     }
     if (aDelay) {
-      return FireDelayedAccessibleEvent(event, eCoalesceFromSameSubtree);
+      return FireDelayedAccessibleEvent(event);
     }
     return FireAccessibleEvent(event);
   }
 
   // Could not find accessible to show hide yet, so fire on any
   // accessible descendants in this subtree
   nsCOMPtr<nsIContent> content(do_QueryInterface(aDOMNode));
   PRUint32 count = content->GetChildCount();
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -98,46 +98,40 @@ class nsDocAccessible : public nsHyperTe
     NS_IMETHOD Init();
 
     // nsPIAccessNode
     NS_IMETHOD_(nsIFrame *) GetFrame(void);
 
     // nsIAccessibleText
     NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor);
 
-    enum EDupeEventRule { eAllowDupes, eCoalesceFromSameSubtree, eRemoveDupes };
-
     /**
       * Non-virtual method to fire a delayed event after a 0 length timeout
       *
       * @param aEvent - the nsIAccessibleEvent event type
       * @param aDOMNode - DOM node the accesible event should be fired for
       * @param aAllowDupes - eAllowDupes: more than one event of the same type is allowed. 
       *                      eCoalesceFromSameSubtree: if two events are in the same subtree,
       *                                                only the event on ancestor is used
       *                      eRemoveDupes (default): events of the same type are discarded
       *                                              (the last one is used)
       *
       * @param aIsAsynch - set to PR_TRUE if this is not being called from code
       *                    synchronous with a DOM event
       */
     nsresult FireDelayedToolkitEvent(PRUint32 aEvent, nsIDOMNode *aDOMNode,
-                                     EDupeEventRule aAllowDupes = eRemoveDupes,
+                                     nsAccEvent::EEventRule aAllowDupes = nsAccEvent::eRemoveDupes,
                                      PRBool aIsAsynch = PR_FALSE);
 
     /**
      * Fire accessible event in timeout.
      *
      * @param aEvent - the event to fire
-     * @param aAllowDupes - if false then delayed events of the same type and
-     *                      for the same DOM node in the event queue won't
-     *                      be fired.
      */
-    nsresult FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent,
-                                        EDupeEventRule aAllowDupes = eRemoveDupes);
+    nsresult FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent);
 
     void ShutdownChildDocuments(nsIDocShellTreeItem *aStart);
 
   protected:
     virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
     virtual nsresult AddEventListeners();
     virtual nsresult RemoveEventListeners();
     void AddScrollListener();
--- a/accessible/src/base/nsRootAccessible.cpp
+++ b/accessible/src/base/nsRootAccessible.cpp
@@ -403,18 +403,17 @@ void nsRootAccessible::TryFireEarlyLoadE
       return;
     }
   }
   nsCOMPtr<nsIDocShellTreeItem> rootContentTreeItem;
   treeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem));
   NS_ASSERTION(rootContentTreeItem, "No root content tree item");
   if (rootContentTreeItem == treeItem) {
     // No frames or iframes, so we can fire the doc load finished event early
-    FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_INTERNAL_LOAD, aDocNode,
-                            eRemoveDupes);
+    FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_INTERNAL_LOAD, aDocNode);
   }
 }
 
 PRBool nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
                                                   nsIDOMNode *aNode,
                                                   nsIDOMEvent *aFocusEvent,
                                                   PRBool aForceEvent,
                                                   PRBool aIsAsynch)
@@ -501,19 +500,20 @@ PRBool nsRootAccessible::FireAccessibleF
             nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENU_START, menuBarAccessible);
           }
         }
       }
     }
   }
   else if (mCurrentARIAMenubar) {
     nsCOMPtr<nsIAccessibleEvent> menuEndEvent =
-      new nsAccEvent(nsIAccessibleEvent::EVENT_MENU_END, mCurrentARIAMenubar, PR_FALSE);
+      new nsAccEvent(nsIAccessibleEvent::EVENT_MENU_END, mCurrentARIAMenubar,
+                     PR_FALSE, nsAccEvent::eAllowDupes);
     if (menuEndEvent) {
-      FireDelayedAccessibleEvent(menuEndEvent, eAllowDupes);
+      FireDelayedAccessibleEvent(menuEndEvent);
     }
     mCurrentARIAMenubar = nsnull;
   }
 
   NS_IF_RELEASE(gLastFocusedNode);
   gLastFocusedNode = finalFocusNode;
   NS_IF_ADDREF(gLastFocusedNode);
 
@@ -534,17 +534,18 @@ PRBool nsRootAccessible::FireAccessibleF
       // and that's what we care about
       // Make sure we never fire focus for the nsRootAccessible (mDOMNode)
       
 return PR_FALSE;
     }
   }
 
   FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS,
-                          finalFocusNode, eRemoveDupes, aIsAsynch);
+                          finalFocusNode, nsAccEvent::eRemoveDupes,
+                          aIsAsynch);
 
   return PR_TRUE;
 }
 
 void nsRootAccessible::FireCurrentFocusEvent()
 {
   nsCOMPtr<nsIDOMNode> focusedNode = GetCurrentFocus();
   if (!focusedNode) {
--- a/accessible/src/html/nsHTMLSelectAccessible.cpp
+++ b/accessible/src/html/nsHTMLSelectAccessible.cpp
@@ -453,16 +453,20 @@ void nsHTMLSelectListAccessible::CacheCh
 
   nsCOMPtr<nsIContent> selectContent(do_QueryInterface(mDOMNode));
   nsCOMPtr<nsIAccessibilityService> accService(do_GetService("@mozilla.org/accessibilityService;1"));
   if (!selectContent || !accService) {
     mAccChildCount = eChildCountUninitialized;
     return;
   }
 
+  if (mAccChildCount != eChildCountUninitialized) {
+    return;
+  }
+
   mAccChildCount = 0; // Avoid reentry
   PRInt32 childCount = 0;
   nsCOMPtr<nsIAccessible> lastGoodAccessible =
     CacheOptSiblings(accService, selectContent, nsnull, &childCount);
   mAccChildCount = childCount;
 }
 
 /** ----- nsHTMLSelectOptionAccessible ----- */