Bug 807911 - whittle mutation events processing, r=tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 13 Nov 2012 15:29:22 +0900
changeset 113063 a2cdd1e642672e54533648fabb01c2f4a20c4122
parent 113062 34903538f4588b0f6174970bfb1d64a592ad30a7
child 113064 a06a34a849551211c9352c79d09678f31faa1e06
push id17921
push usersurkov.alexander@gmail.com
push dateTue, 13 Nov 2012 06:24:19 +0000
treeherdermozilla-inbound@a2cdd1e64267 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs807911
milestone19.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 807911 - whittle mutation events processing, r=tbsaunde
accessible/src/base/AccEvent.cpp
accessible/src/base/AccEvent.h
accessible/src/base/NotificationController.cpp
accessible/src/base/NotificationController.h
accessible/src/base/nsAccDocManager.cpp
accessible/src/generic/DocAccessible-inl.h
accessible/src/generic/DocAccessible.cpp
accessible/src/generic/DocAccessible.h
accessible/src/html/HTMLImageMapAccessible.cpp
accessible/src/xul/XULTreeAccessible.cpp
accessible/tests/mochitest/events.js
accessible/tests/mochitest/events/test_aria_menu.html
accessible/tests/mochitest/events/test_coalescence.html
accessible/tests/mochitest/events/test_mutation.html
--- a/accessible/src/base/AccEvent.cpp
+++ b/accessible/src/base/AccEvent.cpp
@@ -209,17 +209,16 @@ AccStateChangeEvent::CreateXPCOMObject()
 
 // Note: we pass in eAllowDupes to the base class because we don't support text
 // events coalescence. We fire delayed text change events in DocAccessible but
 // we continue to base the event off the accessible object rather than just the
 // node. This means we won't try to create an accessible based on the node when
 // we are ready to fire the event and so we will no longer assert at that point
 // if the node was removed from the document. Either way, the AT won't work with
 // a defunct accessible so the behaviour should be equivalent.
-// XXX revisit this when coalescence is faster (eCoalesceFromSameSubtree)
 AccTextChangeEvent::
   AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
                      const nsAString& aModifiedText, bool aIsInserted,
                      EIsFromUserInput aIsFromUserInput)
   : AccEvent(aIsInserted ?
              static_cast<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_INSERTED) :
              static_cast<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_REMOVED),
              aAccessible, aIsFromUserInput, eAllowDupes)
@@ -238,37 +237,42 @@ AccTextChangeEvent::CreateXPCOMObject()
 {
   nsAccEvent* event = new nsAccTextChangeEvent(this);
   NS_IF_ADDREF(event);
   return event;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// AccMutationEvent
+// AccReorderEvent
 ////////////////////////////////////////////////////////////////////////////////
 
-AccMutationEvent::
-  AccMutationEvent(uint32_t aEventType, Accessible* aTarget,
-                   nsINode* aTargetNode) :
-  AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceFromSameSubtree)
+uint32_t
+AccReorderEvent::IsShowHideEventTarget(const Accessible* aTarget) const
 {
-  mNode = aTargetNode;
+  uint32_t count = mDependentEvents.Length();
+  for (uint32_t index = count - 1; index < count; index--) {
+    if (mDependentEvents[index]->mAccessible == aTarget &&
+        mDependentEvents[index]->mEventType == nsIAccessibleEvent::EVENT_SHOW ||
+        mDependentEvents[index]->mEventType == nsIAccessibleEvent::EVENT_HIDE) {
+      return mDependentEvents[index]->mEventType;
+    }
+  }
+
+  return 0;
 }
 
-
 ////////////////////////////////////////////////////////////////////////////////
 // AccHideEvent
 ////////////////////////////////////////////////////////////////////////////////
 
 AccHideEvent::
   AccHideEvent(Accessible* aTarget, nsINode* aTargetNode) :
   AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode)
 {
-  mParent = mAccessible->Parent();
   mNextSibling = mAccessible->NextSibling();
   mPrevSibling = mAccessible->PrevSibling();
 }
 
 already_AddRefed<nsAccEvent>
 AccHideEvent::CreateXPCOMObject()
 {
   nsAccEvent* event = new nsAccHideEvent(this);
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -33,20 +33,23 @@ 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 umbrella event on the ancestor
-     //    will be emitted.
-     eCoalesceFromSameSubtree,
+     // eCoalesceReorder : For reorder events from the same subtree or the same
+     //    node, only the umbrella event on the ancestor will be emitted.
+     eCoalesceReorder,
+
+     // eCoalesceMutationTextChange : coalesce text change events caused by
+     // tree mutations of the same tree level.
+     eCoalesceMutationTextChange,
 
     // eCoalesceOfSameType : For events of the same type, only the newest event
     // will be processed.
     eCoalesceOfSameType,
 
     // eCoalesceSelectionChange: coalescence of selection change events.
     eCoalesceSelectionChange,
 
@@ -85,16 +88,17 @@ public:
   /**
    * Down casting.
    */
   enum EventGroup {
     eGenericEvent,
     eStateChangeEvent,
     eTextChangeEvent,
     eMutationEvent,
+    eReorderEvent,
     eHideEvent,
     eShowEvent,
     eCaretMoveEvent,
     eSelectionChangeEvent,
     eTableChangeEvent,
     eVirtualCursorChangeEvent
   };
 
@@ -124,16 +128,17 @@ protected:
 
   bool mIsFromUserInput;
   uint32_t mEventType;
   EEventRule mEventRule;
   nsRefPtr<Accessible> mAccessible;
   nsCOMPtr<nsINode> mNode;
 
   friend class NotificationController;
+  friend class AccReorderEvent;
 };
 
 
 /**
  * Accessible state change event.
  */
 class AccStateChangeEvent: public AccEvent
 {
@@ -192,40 +197,50 @@ public:
     { aModifiedText = mModifiedText; }
 
 private:
   int32_t mStart;
   bool mIsInserted;
   nsString mModifiedText;
 
   friend class NotificationController;
+  friend class AccReorderEvent;
 };
 
 
 /**
  * Base class for show and hide accessible events.
  */
 class AccMutationEvent: public AccEvent
 {
 public:
   AccMutationEvent(uint32_t aEventType, Accessible* aTarget,
-                   nsINode* aTargetNode);
+                   nsINode* aTargetNode) :
+    AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceMutationTextChange)
+  {
+    // Don't coalesce these since they are coalesced by reorder event. Coalesce
+    // contained text change events.
+    mNode = aTargetNode;
+    mParent = mAccessible->Parent();
+  }
+  virtual ~AccMutationEvent() { };
 
   // Event
   static const EventGroup kEventGroup = eMutationEvent;
   virtual unsigned int GetEventGroups() const
   {
     return AccEvent::GetEventGroups() | (1U << eMutationEvent);
   }
 
   // MutationEvent
   bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; }
   bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }
 
 protected:
+  nsRefPtr<Accessible> mParent;
   nsRefPtr<AccTextChangeEvent> mTextChangeEvent;
 
   friend class NotificationController;
 };
 
 
 /**
  * Accessible hide event.
@@ -245,17 +260,16 @@ public:
   }
 
   // AccHideEvent
   Accessible* TargetParent() const { return mParent; }
   Accessible* TargetNextSibling() const { return mNextSibling; }
   Accessible* TargetPrevSibling() const { return mPrevSibling; }
 
 protected:
-  nsRefPtr<Accessible> mParent;
   nsRefPtr<Accessible> mNextSibling;
   nsRefPtr<Accessible> mPrevSibling;
 
   friend class NotificationController;
 };
 
 
 /**
@@ -271,16 +285,67 @@ public:
   virtual unsigned int GetEventGroups() const
   {
     return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
   }
 };
 
 
 /**
+ * Class for reorder accessible event. Takes care about
+ */
+class AccReorderEvent : public AccEvent
+{
+public:
+  AccReorderEvent(Accessible* aTarget) :
+    AccEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget,
+             eAutoDetect, eCoalesceReorder) { }
+  virtual ~AccReorderEvent() { };
+
+  // Event
+  static const EventGroup kEventGroup = eReorderEvent;
+  virtual unsigned int GetEventGroups() const
+  {
+    return AccEvent::GetEventGroups() | (1U << eReorderEvent);
+  }
+
+  /**
+   * Get connected with mutation event.
+   */
+  void AddSubMutationEvent(AccMutationEvent* aEvent)
+    { mDependentEvents.AppendElement(aEvent); }
+
+  /**
+   * Do not emit the reorder event and its connected mutation events.
+   */
+  void DoNotEmitAll()
+  {
+    mEventRule = AccEvent::eDoNotEmit;
+    uint32_t eventsCount = mDependentEvents.Length();
+    for (uint32_t idx = 0; idx < eventsCount; idx++)
+      mDependentEvents[idx]->mEventRule = AccEvent::eDoNotEmit;
+  }
+
+  /**
+   * Return true if the given accessible is a target of connected mutation
+   * event.
+   */
+  uint32_t IsShowHideEventTarget(const Accessible* aTarget) const;
+
+protected:
+  /**
+   * Show and hide events causing this reorder event.
+   */
+  nsTArray<AccMutationEvent*> mDependentEvents;
+
+  friend class NotificationController;
+};
+
+
+/**
  * Accessible caret move event.
  */
 class AccCaretMoveEvent: public AccEvent
 {
 public:
   AccCaretMoveEvent(Accessible* aAccessible, int32_t aCaretOffset);
   AccCaretMoveEvent(nsINode* aNode);
 
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -105,16 +105,20 @@ NotificationController::Shutdown()
   mContentInsertions.Clear();
   mNotifications.Clear();
   mEvents.Clear();
 }
 
 void
 NotificationController::QueueEvent(AccEvent* aEvent)
 {
+  NS_ASSERTION(aEvent->mAccessible && aEvent->mAccessible->IsApplication() ||
+               aEvent->GetDocAccessible() == mDocument,
+               "Queued event belongs to another document!");
+
   if (!mEvents.AppendElement(aEvent))
     return;
 
   // Filter events.
   CoalesceEvents();
 
   // Associate text change with hide event if it wasn't stolen from hiding
   // siblings during coalescence.
@@ -293,195 +297,98 @@ NotificationController::WillRefresh(mozi
   // Process invalidation list of the document after all accessible tree
   // modification are done.
   mDocument->ProcessInvalidationList();
 
   // If a generic notification occurs after this point then we may be allowed to
   // process it synchronously.
   mObservingState = eRefreshObserving;
 
-  // Process only currently queued events.
-  nsTArray<nsRefPtr<AccEvent> > events;
-  events.SwapElements(mEvents);
-
-  uint32_t eventCount = events.Length();
-#ifdef A11Y_LOG
-  if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
-    logging::MsgBegin("EVENTS", "events processing");
-    logging::Address("document", mDocument);
-    logging::MsgEnd();
-  }
-#endif
-
-  for (uint32_t idx = 0; idx < eventCount; idx++) {
-    AccEvent* accEvent = events[idx];
-    if (accEvent->mEventRule != AccEvent::eDoNotEmit) {
-      Accessible* target = accEvent->GetAccessible();
-      if (!target || target->IsDefunct())
-        continue;
-
-      // Dispatch the focus event if target is still focused.
-      if (accEvent->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
-        FocusMgr()->ProcessFocusEvent(accEvent);
-        continue;
-      }
-
-      mDocument->ProcessPendingEvent(accEvent);
-
-      // Fire text change event caused by tree mutation.
-      AccMutationEvent* showOrHideEvent = downcast_accEvent(accEvent);
-      if (showOrHideEvent) {
-        if (showOrHideEvent->mTextChangeEvent)
-          mDocument->ProcessPendingEvent(showOrHideEvent->mTextChangeEvent);
-      }
-    }
-    if (!mDocument)
-      return;
-  }
+  ProcessEventQueue();
+  if (!mDocument)
+    return;
 
   // Stop further processing if there are no new notifications of any kind or
   // events and document load is processed.
   if (mContentInsertions.Length() == 0 && mNotifications.Length() == 0 &&
       mEvents.Length() == 0 && mTextHash.Count() == 0 &&
       mHangingChildDocuments.Length() == 0 &&
       mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
       mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
     mObservingState = eNotObservingRefresh;
   }
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// NotificationController: event queue
-
 void
 NotificationController::CoalesceEvents()
 {
   uint32_t numQueuedEvents = mEvents.Length();
   int32_t tail = numQueuedEvents - 1;
   AccEvent* tailEvent = mEvents[tail];
 
   switch(tailEvent->mEventRule) {
-    case AccEvent::eCoalesceFromSameSubtree:
+    case AccEvent::eCoalesceReorder:
+      CoalesceReorderEvents(tailEvent);
+      break; // case eCoalesceReorder
+
+    case AccEvent::eCoalesceMutationTextChange:
     {
-      // No node means this is application accessible (which is a subject of
-      // reorder events), we do not coalesce events for it currently.
-      if (!tailEvent->mNode)
-        return;
-
-      for (int32_t index = tail - 1; index >= 0; index--) {
+      for (uint32_t index = tail - 1; index < tail; index--) {
         AccEvent* thisEvent = mEvents[index];
-
-        if (thisEvent->mEventType != tailEvent->mEventType)
-          continue; // Different type
-
-        // Skip event for application accessible since no coalescence for it
-        // is supported. Ignore events from different documents since we don't
-        // coalesce them.
-        if (!thisEvent->mNode ||
-            thisEvent->mNode->OwnerDoc() != tailEvent->mNode->OwnerDoc())
+        if (thisEvent->mEventRule != tailEvent->mEventRule)
           continue;
 
-        // Coalesce earlier event for the same target.
-        if (thisEvent->mNode == tailEvent->mNode) {
-          thisEvent->mEventRule = AccEvent::eDoNotEmit;
-          return;
-        }
+        // We don't currently coalesce text change events from show/hide events.
+        if (thisEvent->mEventType != tailEvent->mEventType)
+          continue;
 
-        // If event queue contains an event of the same type and having target
-        // that is sibling of target of newly appended event then apply its
-        // event rule to the newly appended event.
+        // Show events may be duped because of reinsertion (removal is ignored
+        // because initial insertion is not processed). Ignore initial
+        // insertion.
+        if (thisEvent->mAccessible == tailEvent->mAccessible)
+          thisEvent->mEventRule = AccEvent::eDoNotEmit;
 
-        // Coalesce hide and show events for sibling targets.
-        if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE) {
+        AccMutationEvent* tailMutationEvent = downcast_accEvent(tailEvent);
+        AccMutationEvent* thisMutationEvent = downcast_accEvent(thisEvent);
+        if (tailMutationEvent->mParent != thisMutationEvent->mParent)
+          continue;
+
+        // Coalesce text change events for hide and show events.
+        if (thisMutationEvent->IsHide()) {
           AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent);
           AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent);
-          if (thisHideEvent->mParent == tailHideEvent->mParent) {
-            tailEvent->mEventRule = thisEvent->mEventRule;
-
-            // Coalesce text change events for hide events.
-            if (tailEvent->mEventRule != AccEvent::eDoNotEmit)
-              CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
-
-            return;
-          }
-        } else if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_SHOW) {
-          if (thisEvent->mAccessible->Parent() ==
-              tailEvent->mAccessible->Parent()) {
-            tailEvent->mEventRule = thisEvent->mEventRule;
-
-            // Coalesce text change events for show events.
-            if (tailEvent->mEventRule != AccEvent::eDoNotEmit) {
-              AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
-              AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
-              CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
-            }
-
-            return;
-          }
+          CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
+          break;
         }
 
-        // Ignore events unattached from DOM since we don't coalesce them.
-        if (!thisEvent->mNode->IsInDoc())
-          continue;
-
-        // Coalesce events by sibling targets (this is a case for reorder
-        // events).
-        if (thisEvent->mNode->GetParentNode() ==
-            tailEvent->mNode->GetParentNode()) {
-          tailEvent->mEventRule = thisEvent->mEventRule;
-          return;
-        }
-
-        // This and tail events can be anywhere in the tree, make assumptions
-        // for mutation events.
-
-        // Coalesce tail event if tail node is descendant of this node. Stop
-        // processing if tail event is coalesced since all possible descendants
-        // of this node was coalesced before.
-        // Note: more older hide event target (thisNode) can't contain recent
-        // hide event target (tailNode), i.e. be ancestor of tailNode. Skip
-        // this check for hide events.
-        if (tailEvent->mEventType != nsIAccessibleEvent::EVENT_HIDE &&
-            nsCoreUtils::IsAncestorOf(thisEvent->mNode, tailEvent->mNode)) {
-          tailEvent->mEventRule = AccEvent::eDoNotEmit;
-          return;
-        }
-
-        // If this node is a descendant of tail node then coalesce this event,
-        // check other events in the queue. Do not emit thisEvent, also apply
-        // this result to sibling nodes of thisNode.
-        if (nsCoreUtils::IsAncestorOf(tailEvent->mNode, thisEvent->mNode)) {
-          thisEvent->mEventRule = AccEvent::eDoNotEmit;
-          ApplyToSiblings(0, index, thisEvent->mEventType,
-                          thisEvent->mNode, AccEvent::eDoNotEmit);
-          continue;
-        }
-
-      } // for (index)
-
-    } break; // case eCoalesceFromSameSubtree
+        AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
+        AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
+        CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
+        break;
+      }
+    } break; // case eCoalesceMutationTextChange
 
     case AccEvent::eCoalesceOfSameType:
     {
       // Coalesce old events by newer event.
-      for (int32_t index = tail - 1; index >= 0; index--) {
+      for (uint32_t index = tail - 1; index < tail; index--) {
         AccEvent* accEvent = mEvents[index];
         if (accEvent->mEventType == tailEvent->mEventType &&
-            accEvent->mEventRule == tailEvent->mEventRule) {
+          accEvent->mEventRule == tailEvent->mEventRule) {
           accEvent->mEventRule = AccEvent::eDoNotEmit;
           return;
         }
       }
     } break; // case eCoalesceOfSameType
 
     case AccEvent::eRemoveDupes:
     {
       // Check for repeat events, coalesce newly appended event by more older
       // event.
-      for (int32_t index = tail - 1; index >= 0; index--) {
+      for (uint32_t index = tail - 1; index < tail; index--) {
         AccEvent* accEvent = mEvents[index];
         if (accEvent->mEventType == tailEvent->mEventType &&
             accEvent->mEventRule == tailEvent->mEventRule &&
             accEvent->mNode == tailEvent->mNode) {
           tailEvent->mEventRule = AccEvent::eDoNotEmit;
           return;
         }
       }
@@ -508,28 +415,102 @@ NotificationController::CoalesceEvents()
     } break; // eCoalesceSelectionChange
 
     default:
       break; // case eAllowDupes, eDoNotEmit
   } // switch
 }
 
 void
-NotificationController::ApplyToSiblings(uint32_t aStart, uint32_t aEnd,
-                                        uint32_t aEventType, nsINode* aNode,
-                                        AccEvent::EEventRule aEventRule)
+NotificationController::CoalesceReorderEvents(AccEvent* aTailEvent)
 {
-  for (uint32_t index = aStart; index < aEnd; index ++) {
-    AccEvent* accEvent = mEvents[index];
-    if (accEvent->mEventType == aEventType &&
-        accEvent->mEventRule != AccEvent::eDoNotEmit && accEvent->mNode &&
-        accEvent->mNode->GetParentNode() == aNode->GetParentNode()) {
-      accEvent->mEventRule = aEventRule;
+  uint32_t count = mEvents.Length();
+  for (uint32_t index = count - 2; index < count; index--) {
+    AccEvent* thisEvent = mEvents[index];
+
+    // Skip events of different types and targeted to application accessible.
+    if (thisEvent->mEventType != aTailEvent->mEventType ||
+        thisEvent->mAccessible->IsApplication())
+      continue;
+
+    // If thisEvent target is not in document longer, i.e. if it was
+    // removed from the tree then do not emit the event.
+    if (!thisEvent->mAccessible->IsDoc() &&
+        !thisEvent->mAccessible->IsInDocument()) {
+      thisEvent->mEventRule = AccEvent::eDoNotEmit;
+      continue;
+    }
+
+    // Coalesce earlier event of the same target.
+    if (thisEvent->mAccessible == aTailEvent->mAccessible) {
+      if (thisEvent->mEventRule == AccEvent::eDoNotEmit) {
+        AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
+        tailReorder->DoNotEmitAll();
+      } else {
+        thisEvent->mEventRule = AccEvent::eDoNotEmit;
+      }
+
+      return;
     }
-  }
+
+    // If tailEvent contains thisEvent
+    // then
+    //   if show of tailEvent contains a grand parent of thisEvent
+    //   then assert
+    //   else if hide of tailEvent contains a grand parent of thisEvent
+    //   then ignore thisEvent and its show and hide events
+    //   otherwise ignore thisEvent but not its show and hide events
+    Accessible* thisParent = thisEvent->mAccessible;
+    while (thisParent && thisParent != mDocument) {
+      if (thisParent->Parent() == aTailEvent->mAccessible) {
+        AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
+        uint32_t eventType = tailReorder->IsShowHideEventTarget(thisParent);
+
+        if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
+           NS_ERROR("Accessible tree was created after it was modified! Huh?");
+        } else if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
+          AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
+          thisReorder->DoNotEmitAll();
+        } else {
+          thisEvent->mEventRule = AccEvent::eDoNotEmit;
+        }
+
+        return;
+      }
+
+      thisParent = thisParent->Parent();
+    }
+
+    // If tailEvent is contained by thisEvent
+    // then
+    //   if show of thisEvent contains the tailEvent
+    //   then ignore tailEvent
+    //   if hide of thisEvent contains the tailEvent
+    //   then assert
+    //   otherwise ignore tailEvent but not its show and hide events
+    Accessible* tailParent = aTailEvent->mAccessible;
+    while (tailParent && tailParent != mDocument) {
+      if (tailParent->Parent() == thisEvent->mAccessible) {
+        AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
+        AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
+        uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent);
+        if (eventType == nsIAccessibleEvent::EVENT_SHOW)
+          tailReorder->DoNotEmitAll();
+        else if (eventType == nsIAccessibleEvent::EVENT_HIDE)
+          NS_ERROR("Accessible tree was modified after it was removed! Huh?");
+        else
+          aTailEvent->mEventRule = AccEvent::eDoNotEmit;
+
+        return;
+      }
+
+      tailParent = tailParent->Parent();
+    }
+
+  } // for (index)
 }
 
 void
 NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
                                                 AccSelChangeEvent* aThisEvent,
                                                 int32_t aThisIndex)
 {
   aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
@@ -539,17 +520,17 @@ NotificationController::CoalesceSelChang
   if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
     aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
     aTailEvent->mAccessible = aTailEvent->mWidget;
     aThisEvent->mEventRule = AccEvent::eDoNotEmit;
 
     // Do not emit any preceding selection events for same widget if they
     // weren't coalesced yet.
     if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
-      for (int32_t jdx = aThisIndex - 1; jdx >= 0; jdx--) {
+      for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
         AccEvent* prevEvent = mEvents[jdx];
         if (prevEvent->mEventRule == aTailEvent->mEventRule) {
           AccSelChangeEvent* prevSelChangeEvent =
             downcast_accEvent(prevEvent);
           if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
             prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
         }
       }
@@ -688,16 +669,86 @@ NotificationController::CreateTextChange
     return;
 
   aEvent->mTextChangeEvent =
     new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(),
                            aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// NotificationController: event queue
+
+void
+NotificationController::ProcessEventQueue()
+{
+  // Process only currently queued events.
+  nsTArray<nsRefPtr<AccEvent> > events;
+  events.SwapElements(mEvents);
+
+  uint32_t eventCount = events.Length();
+#ifdef A11Y_LOG
+  if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
+    logging::MsgBegin("EVENTS", "events processing");
+    logging::Address("document", mDocument);
+    logging::MsgEnd();
+  }
+#endif
+
+  for (uint32_t idx = 0; idx < eventCount; idx++) {
+    AccEvent* event = events[idx];
+    if (event->mEventRule != AccEvent::eDoNotEmit) {
+      Accessible* target = event->GetAccessible();
+      if (!target || target->IsDefunct())
+        continue;
+
+      // Dispatch the focus event if target is still focused.
+      if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
+        FocusMgr()->ProcessFocusEvent(event);
+        continue;
+      }
+
+      // Dispatch caret moved and text selection change events.
+      if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
+        HyperTextAccessible* hyperText = target->AsHyperText();
+        int32_t caretOffset = -1;
+        if (hyperText &&
+          NS_SUCCEEDED(hyperText->GetCaretOffset(&caretOffset))) {
+          nsRefPtr<AccEvent> caretMoveEvent =
+            new AccCaretMoveEvent(hyperText, caretOffset);
+          nsEventShell::FireEvent(caretMoveEvent);
+
+          // There's a selection so fire selection change as well.
+          int32_t selectionCount;
+          hyperText->GetSelectionCount(&selectionCount);
+          if (selectionCount)
+            nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
+                                    hyperText);
+        }
+        continue;
+      }
+
+      nsEventShell::FireEvent(event);
+
+      // Fire text change events.
+      AccMutationEvent* mutationEvent = downcast_accEvent(event);
+      if (mutationEvent) {
+        if (mutationEvent->mTextChangeEvent)
+          nsEventShell::FireEvent(mutationEvent->mTextChangeEvent);
+      }
+    }
+
+    if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
+      mDocument->ShutdownChildrenInSubtree(event->mAccessible);
+
+    if (!mDocument)
+      return;
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Notification controller: text leaf accessible text update
 
 PLDHashOperator
 NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
                                        void* aUserArg)
 {
   DocAccessible* document = static_cast<DocAccessible*>(aUserArg);
   nsIContent* textNode = aEntry->GetKey();
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -196,28 +196,19 @@ private:
 
   // Event queue processing
   /**
    * Coalesce redundant events from the queue.
    */
   void CoalesceEvents();
 
   /**
-   * 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)
+   * Coalesce events from the same subtree.
    */
-  void ApplyToSiblings(uint32_t aStart, uint32_t aEnd,
-                       uint32_t aEventType, nsINode* aNode,
-                       AccEvent::EEventRule aEventRule);
+  void CoalesceReorderEvents(AccEvent* aTailEvent);
 
   /**
    * Coalesce two selection change events within the same select control.
    */
   void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
                                AccSelChangeEvent* aThisEvent,
                                int32_t aThisIndex);
 
@@ -225,21 +216,28 @@ private:
    * Coalesce text change events caused by sibling hide events.
    */
   void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
                                    AccHideEvent* aThisEvent);
   void CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent,
                                    AccShowEvent* aThisEvent);
 
   /**
-   * Create text change event caused by hide or show event. When a node is
-   * hidden/removed or shown/appended, the text in an ancestor hyper text will
-   * lose or get new characters.
+    * Create text change event caused by hide or show event. When a node is
+    * hidden/removed or shown/appended, the text in an ancestor hyper text will
+    * lose or get new characters.
+    */
+   void CreateTextChangeEventFor(AccMutationEvent* aEvent);
+
+  // Event queue processing
+
+  /**
+   * Process events from the queue and fires events.
    */
-  void CreateTextChangeEventFor(AccMutationEvent* aEvent);
+  void ProcessEventQueue();
 
 private:
   /**
    * Indicates whether we're waiting on an event queue processing from our
    * notification controller to flush events.
    */
   enum eObservingState {
     eNotObservingRefresh,
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -393,19 +393,20 @@ nsAccDocManager::CreateDocOrRootAccessib
       docAcc->Shutdown();
       return nullptr;
     }
 
     // Fire reorder event to notify new accessible document has been attached to
     // the tree. The reorder event is delivered after the document tree is
     // constructed because event processing and tree construction are done by
     // the same document.
+    // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
+    // events processing.
     nsRefPtr<AccEvent> reorderEvent =
-      new AccEvent(nsIAccessibleEvent::EVENT_REORDER, ApplicationAcc(),
-                   eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
+      new AccEvent(nsIAccessibleEvent::EVENT_REORDER, ApplicationAcc());
     docAcc->FireDelayedAccessibleEvent(reorderEvent);
 
   } else {
     parentDocAcc->BindChildDocument(docAcc);
   }
 
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eDocCreate)) {
--- a/accessible/src/generic/DocAccessible-inl.h
+++ b/accessible/src/generic/DocAccessible-inl.h
@@ -44,12 +44,12 @@ DocAccessible::MaybeNotifyOfValueChange(
 {
   mozilla::a11y::role role = aAccessible->Role();
   if (role == mozilla::a11y::roles::ENTRY ||
       role == mozilla::a11y::roles::COMBOBOX) {
     nsRefPtr<AccEvent> valueChangeEvent =
       new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible,
                    eAutoDetect, AccEvent::eRemoveDupes);
     FireDelayedAccessibleEvent(valueChangeEvent);
-    }
   }
+}
 
 #endif
--- a/accessible/src/generic/DocAccessible.cpp
+++ b/accessible/src/generic/DocAccessible.cpp
@@ -1567,19 +1567,17 @@ DocAccessible::DoInitialUpdate()
   // Build initial tree.
   CacheChildrenInSubtree(this);
 
   // Fire reorder event after the document tree is constructed. Note, since
   // this reorder event is processed by parent document then events targeted to
   // this document may be fired prior to this reorder event. If this is
   // a problem then consider to keep event processing per tab document.
   if (!IsRoot()) {
-    nsRefPtr<AccEvent> reorderEvent =
-      new AccEvent(nsIAccessibleEvent::EVENT_REORDER, Parent(), eAutoDetect,
-                   AccEvent::eCoalesceFromSameSubtree);
+    nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
     ParentDocument()->FireDelayedAccessibleEvent(reorderEvent);
   }
 }
 
 void
 DocAccessible::ProcessLoad()
 {
   mLoadState |= eCompletelyLoaded;
@@ -1781,84 +1779,62 @@ DocAccessible::FireDelayedAccessibleEven
 
   if (mNotificationController)
     mNotificationController->QueueEvent(aEvent);
 
   return NS_OK;
 }
 
 void
-DocAccessible::ProcessPendingEvent(AccEvent* aEvent)
-{
-  uint32_t eventType = aEvent->GetEventType();
-  if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
-    HyperTextAccessible* hyperText = aEvent->GetAccessible()->AsHyperText();
-    int32_t caretOffset;
-    if (hyperText &&
-        NS_SUCCEEDED(hyperText->GetCaretOffset(&caretOffset))) {
-      nsRefPtr<AccEvent> caretMoveEvent =
-        new AccCaretMoveEvent(hyperText, caretOffset);
-      nsEventShell::FireEvent(caretMoveEvent);
-
-      int32_t selectionCount;
-      hyperText->GetSelectionCount(&selectionCount);
-      if (selectionCount) {  // There's a selection so fire selection change as well
-        nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
-                                hyperText);
-      }
-    }
-  }
-  else {
-    nsEventShell::FireEvent(aEvent);
-
-    // Post event processing
-    if (eventType == nsIAccessibleEvent::EVENT_HIDE)
-      ShutdownChildrenInSubtree(aEvent->GetAccessible());
-  }
-}
-
-void
 DocAccessible::ProcessContentInserted(Accessible* aContainer,
                                       const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent)
 {
-  // Process the notification if the container accessible is still in tree.
+  // Process insertions if the container accessible is still in tree.
   if (!HasAccessible(aContainer->GetNode()))
     return;
 
-  if (aContainer == this) {
-    // If new root content has been inserted then update it.
-    nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocument);
-    if (rootContent != mContent)
-      mContent = rootContent;
+  bool containerNotUpdated = true;
 
-    // Continue to update the tree even if we don't have root content.
-    // For example, elements may be inserted under the document element while
-    // there is no HTML body element.
-  }
+  for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) {
+    // The container might be changed, for example, because of the subsequent
+    // overlapping content insertion (i.e. other content was inserted between
+    // this inserted content and its container or the content was reinserted
+    // into different container of unrelated part of tree). To avoid a double
+    // processing of the content insertion ignore this insertion notification.
+    // Note, the inserted content might be not in tree at all at this point what
+    // means there's no container. Ignore the insertion too.
+
+    Accessible* presentContainer =
+      GetContainerAccessible(aInsertedContent->ElementAt(idx));
+    if (presentContainer != aContainer)
+      continue;
+
+    if (containerNotUpdated) {
+      containerNotUpdated = false;
 
-  // XXX: Invalidate parent-child relations for container accessible and its
-  // children because there's no good way to find insertion point of new child
-  // accessibles into accessible tree. We need to invalidate children even
-  // there's no inserted accessibles in the end because accessible children
-  // are created while parent recaches child accessibles.
-  aContainer->UpdateChildren();
+      if (aContainer == this) {
+        // If new root content has been inserted then update it.
+        nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocument);
+        if (rootContent != mContent)
+          mContent = rootContent;
 
-  // The container might be changed, for example, because of the subsequent
-  // overlapping content insertion (i.e. other content was inserted between this
-  // inserted content and its container or the content was reinserted into
-  // different container of unrelated part of tree). These cases result in
-  // double processing, however generated events are coalesced and we don't
-  // harm an AT.
-  // Theoretically the element might be not in tree at all at this point what
-  // means there's no container.
-  for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) {
-    Accessible* directContainer =
-      GetContainerAccessible(aInsertedContent->ElementAt(idx));
-    if (directContainer)
-      UpdateTree(directContainer, aInsertedContent->ElementAt(idx), true);
+        // Continue to update the tree even if we don't have root content.
+        // For example, elements may be inserted under the document element while
+        // there is no HTML body element.
+      }
+
+      // XXX: Invalidate parent-child relations for container accessible and its
+      // children because there's no good way to find insertion point of new child
+      // accessibles into accessible tree. We need to invalidate children even
+      // there's no inserted accessibles in the end because accessible children
+      // are created while parent recaches child accessibles.
+      aContainer->UpdateChildren();
+    }
+
+    UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true);
   }
 }
 
 void
 DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
                           bool aIsInsert)
 {
   uint32_t updateFlags = eNoAccessible;
@@ -1875,65 +1851,65 @@ DocAccessible::UpdateTree(Accessible* aC
       logging::Address("child", child);
     else
       logging::MsgEntry("child accessible: null");
 
     logging::MsgEnd();
   }
 #endif
 
+  nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
+
   if (child) {
-    updateFlags |= UpdateTreeInternal(child, aIsInsert);
+    updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
 
   } else {
     nsAccTreeWalker walker(this, aChildNode,
                            aContainer->CanHaveAnonChildren(), true);
 
     while ((child = walker.NextChild()))
-      updateFlags |= UpdateTreeInternal(child, aIsInsert);
+      updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
   }
 
   // Content insertion/removal is not cause of accessible tree change.
   if (updateFlags == eNoAccessible)
     return;
 
   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
   // if it did.
   if (aIsInsert && !(updateFlags & eAlertAccessible)) {
     // XXX: tree traversal is perf issue, accessible should know if they are
     // children of alert accessible to avoid this.
     Accessible* ancestor = aContainer;
     while (ancestor) {
       if (ancestor->ARIARole() == roles::ALERT) {
-        FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT,
-                                   ancestor->GetNode());
+        nsRefPtr<AccEvent> alertEvent =
+          new AccEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
+        FireDelayedAccessibleEvent(alertEvent);
         break;
       }
 
       // Don't climb above this document.
       if (ancestor == this)
         break;
 
       ancestor = ancestor->Parent();
     }
   }
 
   MaybeNotifyOfValueChange(aContainer);
 
   // Fire reorder event so the MSAA clients know the children have changed. Also
   // the event is used internally by MSAA layer.
-  nsRefPtr<AccEvent> reorderEvent =
-    new AccEvent(nsIAccessibleEvent::EVENT_REORDER, aContainer->GetNode(),
-                 eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
-  if (reorderEvent)
-    FireDelayedAccessibleEvent(reorderEvent);
+  FireDelayedAccessibleEvent(reorderEvent);
 }
 
 uint32_t
-DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert)
+DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
+                                  AccReorderEvent* aReorderEvent)
 {
   uint32_t updateFlags = eAccessible;
 
   nsINode* node = aChild->GetNode();
   if (aIsInsert) {
     // Create accessible tree for shown accessible.
     CacheChildrenInSubtree(aChild);
 
@@ -1945,44 +1921,44 @@ DocAccessible::UpdateTreeInternal(Access
     // menu is showing (and that's impossible until bug 606924 is fixed).
     // Nevertheless we should do this at least because layout coalesces
     // the changes before our processing and we may miss some menupopup
     // events. Now we just want to be consistent in content insertion/removal
     // handling.
     if (aChild->ARIARole() == roles::MENUPOPUP) {
       nsRefPtr<AccEvent> event =
         new AccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
-
-      if (event)
-        FireDelayedAccessibleEvent(event);
+      FireDelayedAccessibleEvent(event);
     }
   }
 
   // Fire show/hide event.
-  nsRefPtr<AccEvent> event;
+  nsRefPtr<AccMutationEvent> event;
   if (aIsInsert)
     event = new AccShowEvent(aChild, node);
   else
     event = new AccHideEvent(aChild, node);
 
-  if (event)
-    FireDelayedAccessibleEvent(event);
+  FireDelayedAccessibleEvent(event);
+  aReorderEvent->AddSubMutationEvent(event);
 
   if (aIsInsert) {
     roles::Role ariaRole = aChild->ARIARole();
     if (ariaRole == roles::MENUPOPUP) {
       // Fire EVENT_MENUPOPUP_START if ARIA menu appears.
-      FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
-                                 node, AccEvent::eRemoveDupes);
+      nsRefPtr<AccEvent> event =
+        new AccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
+      FireDelayedAccessibleEvent(event);
 
     } else if (ariaRole == roles::ALERT) {
       // Fire EVENT_ALERT if ARIA alert appears.
       updateFlags = eAlertAccessible;
-      FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, node,
-                                 AccEvent::eRemoveDupes);
+      nsRefPtr<AccEvent> event =
+        new AccEvent(nsIAccessibleEvent::EVENT_ALERT, aChild);
+      FireDelayedAccessibleEvent(event);
     }
 
     // If focused node has been shown then it means its frame was recreated
     // while it's focused. Fire focus event on new focused accessible. If
     // the queue contains focus event for this node then it's suppressed by
     // this one.
     // XXX: do we really want to send focus to focused DOM node not taking into
     // account active item?
--- a/accessible/src/generic/DocAccessible.h
+++ b/accessible/src/generic/DocAccessible.h
@@ -423,22 +423,16 @@ protected:
     void ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute);
 
   /**
    * Process ARIA active-descendant attribute change.
    */
   void ARIAActiveDescendantChanged(nsIContent* aElm);
 
   /**
-   * Process the event when the queue of pending events is untwisted. Fire
-   * accessible events as result of the processing.
-   */
-  void ProcessPendingEvent(AccEvent* aEvent);
-
-  /**
    * Update the accessible tree for inserted content.
    */
   void ProcessContentInserted(Accessible* aContainer,
                               const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent);
 
   /**
    * Used to notify the document to make it process the invalidation list.
    *
@@ -459,17 +453,18 @@ protected:
    * accessible tree. Return one of these flags.
    */
   enum EUpdateTreeFlags {
     eNoAccessible = 0,
     eAccessible = 1,
     eAlertAccessible = 2
   };
 
-  uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert);
+  uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
+                              AccReorderEvent* aReorderEvent);
 
   /**
    * Create accessible tree.
    */
   void CacheChildrenInSubtree(Accessible* aRoot);
 
   /**
    * Remove accessibles in subtree from node to accessible map.
--- a/accessible/src/html/HTMLImageMapAccessible.cpp
+++ b/accessible/src/html/HTMLImageMapAccessible.cpp
@@ -80,26 +80,28 @@ HTMLImageMapAccessible::UpdateChildAreas
   nsImageFrame* imageFrame = do_QueryFrame(mContent->GetPrimaryFrame());
 
   // If image map is not initialized yet then we trigger one time more later.
   nsImageMap* imageMapObj = imageFrame->GetExistingImageMap();
   if (!imageMapObj)
     return;
 
   bool doReorderEvent = false;
+  nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
 
   // Remove areas that are not a valid part of the image map anymore.
   for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) {
     Accessible* area = mChildren.ElementAt(childIdx);
     if (area->GetContent()->GetPrimaryFrame())
       continue;
 
     if (aDoFireEvents) {
-      nsRefPtr<AccEvent> event = new AccHideEvent(area, area->GetContent());
+      nsRefPtr<AccHideEvent> event = new AccHideEvent(area, area->GetContent());
       mDoc->FireDelayedAccessibleEvent(event);
+      reorderEvent->AddSubMutationEvent(event);
       doReorderEvent = true;
     }
 
     RemoveChild(area);
   }
 
   // Insert new areas into the tree.
   uint32_t areaElmCount = imageMapObj->AreaCount();
@@ -113,30 +115,27 @@ HTMLImageMapAccessible::UpdateChildAreas
         break;
 
       if (!InsertChildAt(idx, area)) {
         mDoc->UnbindFromDocument(area);
         break;
       }
 
       if (aDoFireEvents) {
-        nsRefPtr<AccEvent> event = new AccShowEvent(area, areaContent);
+        nsRefPtr<AccShowEvent> event = new AccShowEvent(area, areaContent);
         mDoc->FireDelayedAccessibleEvent(event);
+        reorderEvent->AddSubMutationEvent(event);
         doReorderEvent = true;
       }
     }
   }
 
   // Fire reorder event if needed.
-  if (doReorderEvent) {
-    nsRefPtr<AccEvent> reorderEvent =
-      new AccEvent(nsIAccessibleEvent::EVENT_REORDER, mContent,
-                   eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
+  if (doReorderEvent)
     mDoc->FireDelayedAccessibleEvent(reorderEvent);
-  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLImageMapAccessible: Accessible protected
 
 void
 HTMLImageMapAccessible::CacheChildren()
 {
--- a/accessible/src/xul/XULTreeAccessible.cpp
+++ b/accessible/src/xul/XULTreeAccessible.cpp
@@ -657,21 +657,18 @@ void
 XULTreeAccessible::TreeViewChanged(nsITreeView* aView)
 {
   if (IsDefunct())
     return;
 
   // Fire reorder event on tree accessible on accessible tree (do not fire
   // show/hide events on tree items because it can be expensive to fire them for
   // each tree item.
-  nsRefPtr<AccEvent> reorderEvent =
-    new AccEvent(nsIAccessibleEvent::EVENT_REORDER, this, eAutoDetect,
-                 AccEvent::eCoalesceFromSameSubtree);
-  if (reorderEvent)
-    Document()->FireDelayedAccessibleEvent(reorderEvent);
+  nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
+  Document()->FireDelayedAccessibleEvent(reorderEvent);
 
   // Clear cache.
   ClearCache(mAccessibleCache);
   mTreeView = aView;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeAccessible: protected implementation
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -498,19 +498,21 @@ function eventQueue(aEventType)
   {
     return this.mInvokers[++this.mIndex];
   }
 
   this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
   {
     // Create unified event sequence concatenating expected and unexpected
     // events.
-    this.mEventSeq = ("eventSeq" in aInvoker) ?
-      aInvoker.eventSeq :
-      [ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
+    this.mEventSeq = ("eventSeq" in aInvoker) ? aInvoker.eventSeq : [ ];
+    if (!this.mEventSeq.length && this.mDefEventType) {
+      this.mEventSeq.push(new invokerChecker(this.mDefEventType,
+                                             aInvoker.DOMNode));
+    }
 
     var len = this.mEventSeq.length;
     for (var idx = 0; idx < len; idx++) {
       var seqItem = this.mEventSeq[idx];
       // Allow unexpected events in primary event sequence.
       if (!("unexpected" in this.mEventSeq[idx]))
         seqItem.unexpected = false;
 
--- a/accessible/tests/mochitest/events/test_aria_menu.html
+++ b/accessible/tests/mochitest/events/test_aria_menu.html
@@ -62,17 +62,17 @@
         if (aHow == kViaDisplayStyle)
           this.menuNode.style.display = "block";
         else
           this.menuNode.style.visibility = "visible";
       };
 
       this.getID = function showMenu_getID()
       {
-        return "Show ARIA menu " + aMenuID + " by " +
+        return "Show ARIA menu '" + aMenuID + "' by " +
           (aHow == kViaDisplayStyle ? "display" : "visibility") +
           " style tricks";
       };
     }
 
     function closeMenu(aMenuID, aParentMenuID, aHow)
     {
       this.menuNode = getNode(aMenuID);
@@ -145,17 +145,16 @@
       }
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
     var gQueue = null;
 
-    //gA11yEventDumpID = "eventdump"; // debuging
     //gA11yEventDumpToConsole = true; // debuging
 
     function doTests()
     {
       gQueue = new eventQueue();
 
       gQueue.push(new focusMenu("menubar2", "menu-help"));
       gQueue.push(new focusMenu("menubar", "menu-file", "menubar2"));
--- a/accessible/tests/mochitest/events/test_coalescence.html
+++ b/accessible/tests/mochitest/events/test_coalescence.html
@@ -321,17 +321,17 @@
       this.init();
       this.initSequence();
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Do tests.
 
     var gQueue = null;
-    // var gA11yEventDumpID = "eventdump"; // debug stuff
+    //gA11yEventDumpToConsole = true; // debug stuff
 
     function doTests()
     {
       gQueue = new eventQueue();
 
       gQueue.push(new removeChildNParent("option1", "select1"));
       gQueue.push(new removeParentNChild("option2", "select2"));
       gQueue.push(new hideChildNParent("option3", "select3"));
--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -237,18 +237,18 @@
       }
 
       this.newElm = this.DOMNode.cloneNode(true);
       this.newElm.removeAttribute('id');
       this.setTarget(kShowEvent, this.newElm);
     }
 
     /**
-     * Trigger content insertion, removal and insertion of the same element
-     * for the same parent.
+     * Trigger content insertion (flush layout), removal and insertion of
+     * the same element for the same parent.
      */
     function test1(aContainerID)
     {
       this.divNode = document.createElement("div");
       this.divNode.setAttribute("id", "div-test1");
       this.containerNode = getNode(aContainerID);
 
       this.eventSeq = [
@@ -261,23 +261,24 @@
         this.containerNode.appendChild(this.divNode);
         getComputedStyle(this.divNode, "").color;
         this.containerNode.removeChild(this.divNode);
         this.containerNode.appendChild(this.divNode);
       }
 
       this.getID = function test1_getID()
       {
-        return "test1";
+        return "fuzzy test #1: content insertion (flush layout), removal and" +
+          "reinsertion";
       }
     }
 
     /**
-     * Trigger content insertion, removal and insertion of the same element
-     * for the different parents.
+     * Trigger content insertion (flush layout), removal and insertion of
+     * the same element for the different parents.
      */
     function test2(aContainerID, aTmpContainerID)
     {
       this.divNode = document.createElement("div");
       this.divNode.setAttribute("id", "div-test2");
       this.containerNode = getNode(aContainerID);
       this.tmpContainerNode = getNode(aTmpContainerID);
       this.container = getAccessible(this.containerNode);
@@ -297,17 +298,46 @@
         this.tmpContainerNode.appendChild(this.divNode);
         getComputedStyle(this.divNode, "").color;
         this.tmpContainerNode.removeChild(this.divNode);
         this.containerNode.appendChild(this.divNode);
       }
 
       this.getID = function test2_getID()
       {
-        return "test2";
+        return "fuzzy test #2: content insertion (flush layout), removal and" +
+          "reinsertion under another container";
+      }
+    }
+
+    /**
+     * Content insertion (flush layout) and then removal (nothing was changed).
+     */
+    function test3(aContainerID)
+    {
+      this.divNode = document.createElement("div");
+      this.divNode.setAttribute("id", "div-test3");
+      this.containerNode = getNode(aContainerID);
+
+      this.unexpectedEventSeq = [
+        new invokerChecker(EVENT_SHOW, this.divNode),
+        new invokerChecker(EVENT_HIDE, this.divNode),
+        new invokerChecker(EVENT_REORDER, this.containerNode)
+      ];
+
+      this.invoke = function test3_invoke()
+      {
+        this.containerNode.appendChild(this.divNode);
+        getComputedStyle(this.divNode, "").color;
+        this.containerNode.removeChild(this.divNode);
+      }
+
+      this.getID = function test3_getID()
+      {
+        return "fuzzy test #3: content insertion (flush layout) and removal";
       }
     }
 
     /**
      * Target getters.
      */
     function getFirstChild(aNode)
     {
@@ -338,18 +368,17 @@
     {
       return aNode.parentNode;
     }
 
     /**
      * Do tests.
      */
     var gQueue = null;
-    //gA11yEventDumpID = "eventdump"; // debug stuff
-    //gA11yEventDumpToConsole = true;
+    //gA11yEventDumpToConsole = true; // debug stuff
 
     function doTests()
     {
       gQueue = new eventQueue();
 
       // Show/hide events by changing of display style of accessible DOM node
       // from 'inline' to 'none', 'none' to 'inline'.
       var id = "link1";
@@ -422,16 +451,17 @@
 
       gQueue.push(new changeClass("container3", "link8", "", kShowEvents));
       gQueue.push(new changeClass("container3", "link8", "visibilityHidden",
                                   kHideEvents));
 
       gQueue.push(new test1("testContainer"));
       gQueue.push(new test2("testContainer", "testContainer2"));
       gQueue.push(new test2("testContainer", "testNestedContainer"));
+      gQueue.push(new test3("testContainer"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>