Bug 656225 - XUL listbox accessible tree doens't get updated, r=tbsaunde, bz, a=akeybl
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 27 Mar 2012 16:29:51 +0900
changeset 92018 c77d34d9dc8c18ce8351a1eaa6602622f08b5537
parent 92017 8ea9983dc06325d078558809bd67967f937db3a6
child 92019 b0fa8e8ffbc0bc9e6c2e9b0e3b3aa22f9f3c529b
push idunknown
push userunknown
push dateunknown
reviewerstbsaunde, bz, akeybl
bugs656225
milestone13.0a2
Bug 656225 - XUL listbox accessible tree doens't get updated, r=tbsaunde, bz, a=akeybl
accessible/tests/mochitest/role.js
accessible/tests/mochitest/treeupdate/Makefile.in
accessible/tests/mochitest/treeupdate/test_listbox.xul
layout/base/nsCSSFrameConstructor.cpp
layout/xul/base/src/nsListBoxBodyFrame.cpp
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -48,16 +48,17 @@ const ROLE_PAGETABLIST = nsIAccessibleRo
 const ROLE_PANE = nsIAccessibleRole.ROLE_PANE;
 const ROLE_PARAGRAPH = nsIAccessibleRole.ROLE_PARAGRAPH;
 const ROLE_PARENT_MENUITEM = nsIAccessibleRole.ROLE_PARENT_MENUITEM;
 const ROLE_PASSWORD_TEXT = nsIAccessibleRole.ROLE_PASSWORD_TEXT;
 const ROLE_PROGRESSBAR = nsIAccessibleRole.ROLE_PROGRESSBAR;
 const ROLE_PROPERTYPAGE = nsIAccessibleRole.ROLE_PROPERTYPAGE;
 const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON;
 const ROLE_RADIOBUTTON = nsIAccessibleRole.ROLE_RADIOBUTTON;
+const ROLE_RICH_OPTION = nsIAccessibleRole.ROLE_RICH_OPTION;
 const ROLE_ROW = nsIAccessibleRole.ROLE_ROW;
 const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER;
 const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR;
 const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION;
 const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR;
 const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER;
 const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT;
 const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE;
--- a/accessible/tests/mochitest/treeupdate/Makefile.in
+++ b/accessible/tests/mochitest/treeupdate/Makefile.in
@@ -51,16 +51,17 @@ include $(topsrcdir)/config/rules.mk
 		test_colorpicker.xul \
 		test_cssoverflow.html \
 		test_contextmenu.xul \
 		test_doc.html \
 		test_gencontent.html \
 		test_hidden.html \
 		test_list_editabledoc.html \
 		test_list.html \
+		test_listbox.xul \
 		test_menu.xul \
 		test_menubutton.xul \
 		test_recreation.html \
 		test_select.html \
 		test_textleaf.html \
 		test_visibility.html \
 		test_whitespace.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_listbox.xul
@@ -0,0 +1,180 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="Accessible XUL listbox hierarchy tests">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+  <script type="application/javascript"
+          src="../common.js" />
+  <script type="application/javascript"
+          src="../role.js" />
+  <script type="application/javascript"
+          src="../events.js" />
+
+  <script type="application/javascript">
+  <![CDATA[
+    ////////////////////////////////////////////////////////////////////////////
+    // Test
+
+    function insertListitem(aListboxID)
+    {
+      this.listboxNode = getNode(aListboxID);
+
+      this.listitemNode = document.createElement("listitem");
+      this.listitemNode.setAttribute("label", "item1");
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, this.listitemNode),
+        new invokerChecker(EVENT_REORDER, this.listboxNode)
+      ];
+
+      this.invoke = function insertListitem_invoke()
+      {
+        this.listboxNode.insertBefore(this.listitemNode,
+                                      this.listboxNode.firstChild);
+      }
+
+      this.finalCheck = function insertListitem_finalCheck()
+      {
+        var tree =
+          { LISTBOX: [
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item1"
+            },
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item2"
+            },
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item3"
+            },
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item4"
+            }
+          ] };
+        testAccessibleTree(this.listboxNode, tree);
+      }
+
+      this.getID = function insertListitem_getID()
+      {
+        return "insert listitem ";
+      }
+    }
+
+    function removeListitem(aListboxID)
+    {
+      this.listboxNode = getNode(aListboxID);
+      this.listitemNode = null;
+      this.listitem;
+
+      function getListitem(aThisObj)
+      {
+        return aThisObj.listitem;
+      }
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getListitem, this),
+        new invokerChecker(EVENT_REORDER, this.listboxNode)
+      ];
+
+      this.invoke = function removeListitem_invoke()
+      {
+        this.listitemNode = this.listboxNode.firstChild;
+        this.listitem = getAccessible(this.listitemNode);
+
+        this.listboxNode.removeChild(this.listitemNode);
+      }
+
+      this.finalCheck = function removeListitem_finalCheck()
+      {
+        var tree =
+          { LISTBOX: [
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item2"
+            },
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item3"
+            },
+            {
+              role: ROLE_RICH_OPTION,
+              name: "item4"
+            }
+          ] };
+        testAccessibleTree(this.listboxNode, tree);
+      }
+
+      this.getID = function removeListitem_getID()
+      {
+        return "remove listitem ";
+      }
+    }
+
+    //gA11yEventDumpToConsole = true; // debug stuff
+
+    var gQueue = null;
+    function doTest()
+    {
+      var tree =
+        { LISTBOX: [
+          {
+            role: ROLE_RICH_OPTION,
+            name: "item2"
+          },
+          {
+            role: ROLE_RICH_OPTION,
+            name: "item3"
+          },
+          {
+            role: ROLE_RICH_OPTION,
+            name: "item4"
+          }
+        ] };
+      testAccessibleTree("listbox", tree);
+
+      gQueue = new eventQueue();
+      gQueue.push(new insertListitem("listbox"));
+      gQueue.push(new removeListitem("listbox"));
+      gQueue.invoke(); // Will call SimpleTest.finish()
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  ]]>
+  </script>
+
+  <hbox flex="1" style="overflow: auto;">
+    <body xmlns="http://www.w3.org/1999/xhtml">
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=656225"
+         title="XUL listbox accessible tree doesn't get updated">
+        Mozilla Bug 656225
+      </a>
+      <br/>
+      <p id="display"></p>
+      <div id="content" style="display: none">
+      </div>
+      <pre id="test">
+      </pre>
+    </body>
+
+    <vbox flex="1">
+      <listbox id="listbox" rows="2">
+        <listitem label="item2"/>
+        <listitem label="item3"/>
+        <listitem label="item4"/>
+      </listbox>
+    </vbox>
+  </hbox>
+
+</window>
+
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -10542,25 +10542,35 @@ nsCSSFrameConstructor::CreateListBoxCont
                                       aChild->Tag(), aChild->GetNameSpaceID(),
                                       true, styleContext,
                                       ITEM_ALLOW_XBL_BASE, items);
     ConstructFramesFromItemList(state, items, aParentFrame, frameItems);
 
     nsIFrame* newFrame = frameItems.FirstChild();
     *aNewFrame = newFrame;
 
-    if (NS_SUCCEEDED(rv) && (nsnull != newFrame)) {
+    if (newFrame) {
       // Notify the parent frame
       if (aIsAppend)
         rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxAppendFrames(frameItems);
       else
         rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxInsertFrames(aPrevFrame, frameItems);
     }
 
     EndUpdate();
+
+#ifdef ACCESSIBILITY
+    if (newFrame) {
+      nsAccessibilityService* accService = nsIPresShell::AccService();
+      if (accService) {
+        accService->ContentRangeInserted(mPresShell, aChild->GetParent(),
+                                         aChild, aChild->GetNextSibling());
+      }
+    }
+#endif
   }
 
   return rv;
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
--- a/layout/xul/base/src/nsListBoxBodyFrame.cpp
+++ b/layout/xul/base/src/nsListBoxBodyFrame.cpp
@@ -65,16 +65,20 @@
 #include "nsPIBoxObject.h"
 #include "nsINodeInfo.h"
 #include "nsLayoutUtils.h"
 #include "nsPIListBoxObject.h"
 #include "nsContentUtils.h"
 #include "nsChildIterator.h"
 #include "nsRenderingContext.h"
 
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
 /////////////// nsListScrollSmoother //////////////////
 
 /* A mediator used to smooth out scrolling. It works by seeing if 
  * we have time to scroll the amount of rows requested. This is determined
  * by measuring how long it takes to scroll a row. If we can scroll the 
  * rows in time we do so. If not we start a timer and skip the request. We
  * do this until the timer finally first because the user has stopped moving
  * the mouse. Then do all the queued requests in on shot.
@@ -1510,16 +1514,25 @@ nsListBoxBodyFrame::RemoveChildFrame(nsB
     return;
   }
 
   if (aFrame == GetContentInsertionFrame()) {
     // Don't touch that one
     return;
   }
 
+#ifdef ACCESSIBILITY
+  nsAccessibilityService* accService = nsIPresShell::AccService();
+  if (accService) {
+    nsIContent* content = aFrame->GetContent();
+    accService->ContentRemoved(PresContext()->PresShell(), content->GetParent(),
+                               content);
+  }
+#endif
+
   mFrames.RemoveFrame(aFrame);
   if (mLayoutManager)
     mLayoutManager->ChildrenRemoved(this, aState, aFrame);
   aFrame->Destroy();
 }
 
 // Creation Routines ///////////////////////////////////////////////////////////////////////