Bug 1113389 - loading google creates accessibles without firing show events, r=tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 30 Dec 2014 15:43:49 -0500
changeset 247518 c69a53cb9a040d9fbf0c5e692f1ba84d0ab7d655
parent 247517 d227893d32042cee83c8163c754fb6585ff78638
child 247519 4912606fb9c77c120db83256224eeafabc57acc1
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs1113389
milestone37.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 1113389 - loading google creates accessibles without firing show events, r=tbsaunde
accessible/generic/Accessible-inl.h
accessible/generic/Accessible.cpp
accessible/generic/Accessible.h
accessible/generic/DocAccessible.cpp
accessible/generic/DocAccessible.h
accessible/html/HTMLSelectAccessible.cpp
accessible/tests/mochitest/events/test_mutation.html
accessible/tests/mochitest/tree/test_select.html
accessible/tests/mochitest/treeupdate/test_optgroup.html
accessible/tests/mochitest/treeupdate/test_select.html
--- a/accessible/generic/Accessible-inl.h
+++ b/accessible/generic/Accessible-inl.h
@@ -62,20 +62,12 @@ Accessible::HasNumericValue() const
 
 inline void
 Accessible::ScrollTo(uint32_t aHow) const
 {
   if (mContent)
     nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow);
 }
 
-inline bool
-Accessible::UpdateChildren()
-{
-  AutoTreeMutation mut(this);
-  InvalidateChildren();
-  return EnsureChildren();
-}
-
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -2007,18 +2007,17 @@ Accessible::Language(nsAString& aLanguag
   }
 }
 
 void
 Accessible::InvalidateChildren()
 {
   int32_t childCount = mChildren.Length();
   for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
-    Accessible* child = mChildren.ElementAt(childIdx);
-    child->UnbindFromParent();
+    mChildren.ElementAt(childIdx)->UnbindFromParent();
   }
 
   mEmbeddedObjCollector = nullptr;
   mChildren.Clear();
   SetChildrenFlag(eChildrenUninitialized);
 }
 
 bool
@@ -2441,33 +2440,27 @@ Accessible::TestChildCache(Accessible* a
       break;
   }
 
   NS_ASSERTION(child == aCachedChild,
                "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!");
 #endif
 }
 
-// Accessible public
-bool
+void
 Accessible::EnsureChildren()
 {
-  if (IsDefunct()) {
-    SetChildrenFlag(eChildrenUninitialized);
-    return true;
-  }
+  NS_ASSERTION(!IsDefunct(), "Caching children for defunct accessible!");
 
   if (!IsChildrenFlag(eChildrenUninitialized))
-    return false;
+    return;
 
   // State is embedded children until text leaf accessible is appended.
   SetChildrenFlag(eEmbeddedChildren); // Prevent reentry
-
   CacheChildren();
-  return false;
 }
 
 Accessible*
 Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const
 {
   if (!mParent || mIndexInParent == -1) {
     if (aError)
       *aError = NS_ERROR_UNEXPECTED;
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -361,24 +361,19 @@ public:
 
   /**
    * Set the ARIA role map entry for a new accessible.
    */
   void SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
     { mRoleMapEntry = aRoleMapEntry; }
 
   /**
-   * Update the children cache.
+   * Cache children if necessary.
    */
-  bool UpdateChildren();
-
-  /**
-   * Cache children if necessary. Return true if the accessible is defunct.
-   */
-  bool EnsureChildren();
+  void EnsureChildren();
 
   /**
    * Set the child count to -1 (unknown) and null out cached child pointers.
    * Should be called when accessible tree is changed because document has
    * transformed. Note, if accessible cares about its parent relation chain
    * itself should override this method to do nothing.
    */
   virtual void InvalidateChildren();
@@ -582,16 +577,17 @@ public:
 
   bool IsDoc() const { return HasGenericType(eDocument); }
   DocAccessible* AsDoc();
 
   bool IsHyperText() const { return HasGenericType(eHyperText); }
   HyperTextAccessible* AsHyperText();
 
   bool IsHTMLBr() const { return mType == eHTMLBRType; }
+  bool IsHTMLCombobox() const { return mType == eHTMLComboboxType; }
   bool IsHTMLFileInput() const { return mType == eHTMLFileInputType; }
 
   bool IsHTMLListItem() const { return mType == eHTMLLiType; }
   HTMLLIAccessible* AsHTMLListItem();
 
   bool IsHTMLOptGroup() const { return mType == eHTMLOptGroupType; }
 
   bool IsHTMLTable() const { return mType == eHTMLTableType; }
@@ -866,16 +862,29 @@ public:
    * DOM UI event, if otherwise then false. For example, HTMLCheckboxAccessible
    * process nsIDocumentObserver::ContentStateChanged instead
    * 'CheckboxStateChange' event.
    */
   bool NeedsDOMUIEvent() const
     { return !(mStateFlags & eIgnoreDOMUIEvent); }
 
   /**
+   * Get/set survivingInUpdate bit on child indicating that parent recollects
+   * its children.
+   */
+  bool IsSurvivingInUpdate() const { return mStateFlags & eSurvivingInUpdate; }
+  void SetSurvivingInUpdate(bool aIsSurviving)
+  {
+    if (aIsSurviving)
+      mStateFlags |= eSurvivingInUpdate;
+    else
+      mStateFlags &= ~eSurvivingInUpdate;
+  }
+
+  /**
    * Return true if this accessible has a parent whose name depends on this
    * accessible.
    */
   bool HasNameDependentParent() const
     { return mContextFlags & eHasNameDependentParent; }
 
 protected:
 
@@ -948,18 +957,19 @@ protected:
     eIsDefunct = 1 << 0, // accessible is defunct
     eIsNotInDocument = 1 << 1, // accessible is not in document
     eSharedNode = 1 << 2, // accessible shares DOM node from another accessible
     eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map
     eHasNumericValue = 1 << 4, // accessible has a numeric value
     eGroupInfoDirty = 1 << 5, // accessible needs to update group info
     eSubtreeMutating = 1 << 6, // subtree is being mutated
     eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
+    eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
 
-    eLastStateFlag = eIgnoreDOMUIEvent
+    eLastStateFlag = eSurvivingInUpdate
   };
 
   /**
    * Flags used for contextual information about the accessible.
    */
   enum ContextFlags {
     eHasNameDependentParent = 1 << 0, // Parent's name depends on this accessible.
 
@@ -1063,17 +1073,17 @@ protected:
   nsCOMPtr<nsIContent> mContent;
   DocAccessible* mDoc;
 
   nsRefPtr<Accessible> mParent;
   nsTArray<nsRefPtr<Accessible> > mChildren;
   int32_t mIndexInParent;
 
   static const uint8_t kChildrenFlagsBits = 2;
-  static const uint8_t kStateFlagsBits = 8;
+  static const uint8_t kStateFlagsBits = 9;
   static const uint8_t kContextFlagsBits = 1;
   static const uint8_t kTypeBits = 6;
   static const uint8_t kGenericTypesBits = 13;
 
   /**
    * Keep in sync with ChildrenFlags, StateFlags, ContextFlags, and AccTypes.
    */
   uint32_t mChildrenFlags : kChildrenFlagsBits;
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1303,29 +1303,20 @@ DocAccessible::RecreateAccessible(nsICon
 void
 DocAccessible::ProcessInvalidationList()
 {
   // Invalidate children of container accessible for each element in
   // invalidation list. Allow invalidation list insertions while container
   // children are recached.
   for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
     nsIContent* content = mInvalidationList[idx];
-    Accessible* accessible = GetAccessible(content);
-    if (!accessible) {
+    if (!HasAccessible(content)) {
       Accessible* container = GetContainerAccessible(content);
-      if (container) {
-        container->UpdateChildren();
-        accessible = GetAccessible(content);
-      }
-    }
-
-    // Make sure the subtree is created.
-    if (accessible) {
-      AutoTreeMutation mut(accessible);
-      CacheChildrenInSubtree(accessible);
+      if (container)
+        UpdateTreeOnInsertion(container);
     }
   }
 
   mInvalidationList.Clear();
 }
 
 Accessible*
 DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
@@ -1628,138 +1619,101 @@ DocAccessible::UpdateAccessibleOnAttrCha
 void
 DocAccessible::ProcessContentInserted(Accessible* aContainer,
                                       const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent)
 {
   // Process insertions if the container accessible is still in tree.
   if (!HasAccessible(aContainer->GetNode()))
     return;
 
-  bool containerNotUpdated = true;
-
   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 =
+    Accessible* container =
       GetContainerAccessible(aInsertedContent->ElementAt(idx));
-    if (presentContainer != aContainer)
+    if (container != aContainer)
       continue;
 
-    if (containerNotUpdated) {
-      containerNotUpdated = false;
-
-      if (aContainer == this) {
-        // If new root content has been inserted then update it.
-        nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode);
-        if (rootContent != mContent) {
-          mContent = rootContent;
-          SetRoleMapEntry(aria::GetRoleMap(mContent));
-        }
-
-        // 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.
+    if (container == this) {
+      // If new root content has been inserted then update it.
+      nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode);
+      if (rootContent != mContent) {
+        mContent = rootContent;
+        SetRoleMapEntry(aria::GetRoleMap(mContent));
       }
 
-      // 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.
-      // XXX Group invalidation here may be redundant with invalidation in
-      // UpdateTree.
-      AutoTreeMutation mut(aContainer);
-      aContainer->InvalidateChildren();
-      CacheChildrenInSubtree(aContainer);
+      // 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.
     }
 
-    UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true);
+    // HTML comboboxes have no-content list accessible as an intermidiate
+    // containing all options.
+    if (container->IsHTMLCombobox())
+      container = container->FirstChild();
+
+    // We have a DOM/layout change under the container accessible, and its tree
+    // might need an update. Since DOM/layout change of the element may affect
+    // on the accessibleness of adjacent elements (for example, insertion of
+    // extra HTML:body make the old body accessible) then we have to recache
+    // children of the container, and then fire show/hide events for a change.
+    UpdateTreeOnInsertion(container);
+    break;
   }
 }
 
 void
-DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
-                          bool aIsInsert)
+DocAccessible::UpdateTreeOnInsertion(Accessible* aContainer)
 {
-  uint32_t updateFlags = eNoAccessible;
+  for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) {
+    Accessible* child = aContainer->ContentChildAt(idx);
+    child->SetSurvivingInUpdate(true);
+   }
 
-  // If child node is not accessible then look for its accessible children.
-  Accessible* child = GetAccessible(aChildNode);
-#ifdef A11Y_LOG
-  if (logging::IsEnabled(logging::eTree)) {
-    logging::MsgBegin("TREE", "process content %s",
-                      (aIsInsert ? "insertion" : "removal"));
-    logging::Node("container", aContainer->GetNode());
-    logging::Node("child", aChildNode);
-    if (child)
-      logging::Address("child", child);
-    else
-      logging::MsgEntry("child accessible: null");
-
-    logging::MsgEnd();
-  }
-#endif
+  AutoTreeMutation mut(aContainer);
+  aContainer->InvalidateChildren();
+  aContainer->EnsureChildren();
 
   nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
-  AutoTreeMutation mut(aContainer);
 
-  if (child) {
-    updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
-  } else {
-    if (aIsInsert) {
-      TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
+  uint32_t updateFlags = eNoAccessible;
+  for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) {
+    Accessible* child = aContainer->ContentChildAt(idx);
+    if (child->IsSurvivingInUpdate()) {
+      child->SetSurvivingInUpdate(false);
+      continue;
+    }
 
-      while ((child = walker.NextChild()))
-        updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
-    } else {
-      // aChildNode may not coorespond to a particular accessible, to handle
-      // this we go through all the children of aContainer.  Then if a child
-      // has aChildNode as an ancestor, or does not have the node for
-      // aContainer as an ancestor remove that child of aContainer.  Note that
-      // when we are called aChildNode may already have been removed
-      // from the DOM so we can't expect it to have a parent or what was it's
-      // parent to have it as a child.
-      nsINode* containerNode = aContainer->GetNode();
-      for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
-        Accessible* child = aContainer->ContentChildAt(idx);
+    // A new child has been created, update its tree.
+#ifdef A11Y_LOG
+    if (logging::IsEnabled(logging::eTree)) {
+      logging::MsgBegin("TREE", "process content insertion");
+      logging::Node("container", aContainer->GetNode());
+      logging::Node("child", child->GetContent());
+      logging::Address("child", child);
+      logging::MsgEnd();
+    }
+#endif
 
-        // If accessible doesn't have its own content then we assume parent
-        // will handle its update.  If child is DocAccessible then we don't
-        // handle updating it here either.
-        if (!child->HasOwnContent() || child->IsDoc()) {
-          idx++;
-          continue;
-        }
-
-        nsINode* childNode = child->GetContent();
-        while (childNode != aChildNode && childNode != containerNode &&
-               (childNode = childNode->GetParentNode()));
-
-        if (childNode != containerNode) {
-          updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
-        } else {
-          idx++;
-        }
-      }
-    }
+    updateFlags |= UpdateTreeInternal(child, true, 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)) {
+  if (!(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) {
         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
         break;
       }
@@ -1768,19 +1722,81 @@ DocAccessible::UpdateTree(Accessible* aC
       if (ancestor == this)
         break;
 
       ancestor = ancestor->Parent();
     }
   }
 
   MaybeNotifyOfValueChange(aContainer);
+  FireDelayedEvent(reorderEvent);
+}
 
-  // Fire reorder event so the MSAA clients know the children have changed. Also
-  // the event is used internally by MSAA layer.
+void
+DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
+{
+  // If child node is not accessible then look for its accessible children.
+  Accessible* child = GetAccessible(aChildNode);
+#ifdef A11Y_LOG
+  if (logging::IsEnabled(logging::eTree)) {
+    logging::MsgBegin("TREE", "process content removal");
+    logging::Node("container", aContainer->GetNode());
+    logging::Node("child", aChildNode);
+    if (child)
+      logging::Address("child", child);
+    else
+      logging::MsgEntry("child accessible: null");
+
+    logging::MsgEnd();
+  }
+#endif
+
+  uint32_t updateFlags = eNoAccessible;
+  nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
+  AutoTreeMutation mut(aContainer);
+
+  if (child) {
+    updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
+  } else {
+    // aChildNode may not coorespond to a particular accessible, to handle
+    // this we go through all the children of aContainer.  Then if a child
+    // has aChildNode as an ancestor, or does not have the node for
+    // aContainer as an ancestor remove that child of aContainer.  Note that
+    // when we are called aChildNode may already have been removed from the DOM
+    // so we can't expect it to have a parent or what was it's parent to have
+    // it as a child.
+    nsINode* containerNode = aContainer->GetNode();
+    for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
+      Accessible* child = aContainer->ContentChildAt(idx);
+
+      // If accessible doesn't have its own content then we assume parent
+      // will handle its update.  If child is DocAccessible then we don't
+      // handle updating it here either.
+      if (!child->HasOwnContent() || child->IsDoc()) {
+        idx++;
+        continue;
+      }
+
+      nsINode* childNode = child->GetContent();
+      while (childNode != aChildNode && childNode != containerNode &&
+             (childNode = childNode->GetParentNode()));
+
+      if (childNode != containerNode) {
+        updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
+      } else {
+        idx++;
+      }
+    }
+  }
+
+  // Content insertion/removal is not cause of accessible tree change.
+  if (updateFlags == eNoAccessible)
+    return;
+
+  MaybeNotifyOfValueChange(aContainer);
   FireDelayedEvent(reorderEvent);
 }
 
 uint32_t
 DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
                                   AccReorderEvent* aReorderEvent)
 {
   uint32_t updateFlags = eAccessible;
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -316,17 +316,17 @@ public:
 
   /**
    * Notify the document accessible that content was removed.
    */
   void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode)
   {
     // Update the whole tree of this document accessible when the container is
     // null (document element is removed).
-    UpdateTree((aContainer ? aContainer : this), aChildNode, false);
+    UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode);
   }
   void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode)
   {
     ContentRemoved(GetAccessibleOrContainer(aContainerNode), aChildNode);
   }
 
   /**
    * Updates accessible tree when rendered text is changed.
@@ -460,23 +460,27 @@ protected:
    *
    * While children are cached we may encounter the case there's no accessible
    * for referred content by related accessible. Store these related nodes to
    * invalidate their containers later.
    */
   void ProcessInvalidationList();
 
   /**
-   * Update the accessible tree for content insertion or removal.
+   * Update the tree on content insertion.
    */
-  void UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
-                  bool aIsInsert);
+  void UpdateTreeOnInsertion(Accessible* aContainer);
 
   /**
-   * Helper for UpdateTree() method. Go down to DOM subtree and updates
+   * Update the accessible tree for content removal.
+   */
+  void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode);
+
+  /**
+   * Helper for UpdateTreeOn methods. Go down to DOM subtree and updates
    * accessible tree. Return one of these flags.
    */
   enum EUpdateTreeFlags {
     eNoAccessible = 0,
     eAccessible = 1,
     eAlertAccessible = 2
   };
 
--- a/accessible/html/HTMLSelectAccessible.cpp
+++ b/accessible/html/HTMLSelectAccessible.cpp
@@ -354,16 +354,17 @@ HTMLSelectOptGroupAccessible::DoAction(u
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLComboboxAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 HTMLComboboxAccessible::
   HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   AccessibleWrap(aContent, aDoc)
 {
+  mType = eHTMLComboboxType;
   mGenericTypes |= eCombobox;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLComboboxAccessible: Accessible
 
 role
 HTMLComboboxAccessible::NativeRole()
--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -331,23 +331,49 @@
       }
 
       this.getID = function test3_getID()
       {
         return "fuzzy test #3: content insertion (flush layout) and removal";
       }
     }
 
+    function insertReferredElm(aContainerID)
+    {
+      this.containerNode = getNode(aContainerID);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode),
+        new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode),
+        new invokerChecker(EVENT_REORDER, this.containerNode)
+      ];
+
+      this.invoke = function insertReferredElm_invoke()
+      {
+        this.containerNode.innerHTML =
+          "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>";
+      }
+
+      this.getID = function insertReferredElm_getID()
+      {
+        return "insert inaccessible element and then insert referring element to make it accessible";
+      }
+    }
+
     /**
      * Target getters.
      */
     function getFirstChild(aNode)
     {
       return [aNode.firstChild];
     }
+    function getLastChild(aNode)
+    {
+      return [aNode.lastChild];
+    }
 
     function getNEnsureFirstChild(aNode)
     {
       var node = aNode.firstChild;
       getAccessible(node);
       return [node];
     }
 
@@ -452,16 +478,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.push(new insertReferredElm("testContainer3"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
@@ -511,10 +538,11 @@
 
     <a id="link6" href="http://www.google.com">Link #6</a>
 
     <div id="container2" class="displayNone"><a id="link7">Link #7</a></div>
     <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div>
     <div id="testNestedContainer"></div>
   </div>
   <div id="testContainer2"></div>
+  <div id="testContainer3"></div>
 </body>
 </html>
--- a/accessible/tests/mochitest/tree/test_select.html
+++ b/accessible/tests/mochitest/tree/test_select.html
@@ -81,17 +81,17 @@
                     role: ROLE_COMBOBOX_OPTION,
                     children: [
                       {
                         role: ROLE_TEXT_LEAF
                       }
                     ]
                   },
                 ]
-},
+              },
               {
                 role: ROLE_COMBOBOX_OPTION,
                 children: [
                   {
                     role: ROLE_TEXT_LEAF
                   }
                 ]
               }
--- a/accessible/tests/mochitest/treeupdate/test_optgroup.html
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -16,16 +16,17 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
 
     function addOptGroup(aID)
     {
       this.selectNode = getNode(aID);
       this.select = getAccessible(this.selectNode);
+      this.selectList = this.select.firstChild;
 
       this.invoke = function addOptGroup_invoke()
       {
         var optGroup = document.createElement("optgroup");
         for (i = 0; i < 2; i++) {
           var opt = document.createElement("option");
           opt.value = i;
           opt.text = "Option: Value " + i;
@@ -34,17 +35,17 @@
         }
 
         this.selectNode.add(optGroup, null);
         var option = document.createElement("option");
         this.selectNode.add(option, null);
       }
 
       this.eventSeq = [
-        new invokerChecker(EVENT_REORDER, this.select)
+        new invokerChecker(EVENT_REORDER, this.selectList)
       ];
 
       this.finalCheck = function addOptGroup_finalCheck()
       {
         var tree =
           { COMBOBOX: [
             { COMBOBOX_LIST: [
               { GROUPING: [
--- a/accessible/tests/mochitest/treeupdate/test_select.html
+++ b/accessible/tests/mochitest/treeupdate/test_select.html
@@ -16,30 +16,31 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
 
     function addOptions(aID)
     {
       this.selectNode = getNode(aID);
       this.select = getAccessible(this.selectNode);
+      this.selectList = this.select.firstChild;
 
       this.invoke = function addOptions_invoke()
       {
         for (i = 0; i < 2; i++) {
           var opt = document.createElement("option");
           opt.value = i;
           opt.text = "Option: Value " + i;
 
           this.selectNode.add(opt, null);
         }
       }
 
       this.eventSeq = [
-        new invokerChecker(EVENT_REORDER, this.select)
+        new invokerChecker(EVENT_REORDER, this.selectList)
       ];
 
       this.finalCheck = function addOptions_finalCheck()
       {
         var tree =
           { COMBOBOX: [
             { COMBOBOX_LIST: [
               { COMBOBOX_OPTION: [