diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.java b/user/src/com/google/gwt/user/cellview/client/CellTree.java
index 94b61de117..ea7a2e187a 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.java
@@ -455,7 +455,9 @@ interface BasicStyle extends Style {
}
interface Template extends SafeHtmlTemplates {
- @Template("
{2}
")
+ // The open/closed/loading image is decorative; aria-expanded conveys the open/closed state.
+ @Template(""
+ + "{2}
")
SafeHtml imageWrapper(String classes, SafeStyles cssLayout, SafeHtml image);
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
index 878d80a916..fa09a7e7ab 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -1170,6 +1170,11 @@ Element ensureAnimationFrame() {
Element ensureChildContainer() {
if (childContainer == null) {
childContainer = Document.get().createDivElement();
+ // Nested items need a group between them and their parent item; the root's
+ // children are owned by the tree element itself.
+ if (!isRootNode()) {
+ Roles.getGroupRole().set(childContainer);
+ }
ensureContentContainer().insertFirst(childContainer);
}
return childContainer;
diff --git a/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java b/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java
index 229a7e68ea..11d67a6bb3 100644
--- a/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java
@@ -17,6 +17,8 @@
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.RootPanel;
@@ -267,6 +269,55 @@ public void testAriaAttributes() {
assertEquals("5", cViewChildLast.getElement().getAttribute("aria-setsize"));
}
+ public void testChildContainerRoles() {
+ CellTree cellTree = (CellTree) tree;
+ TreeNode node = cellTree.getRootTreeNode();
+ while (!node.isChildLeaf(0)) {
+ node = node.setChildOpen(0, true);
+ }
+
+ // Top-level items are owned by the tree itself.
+ CellTreeNodeView> view = cellTree.rootNode.getChildNode(0);
+ assertEquals(cellTree.getElement(), getClosestRoleAncestor(view.getElement()));
+
+ // Nested items, expandable or leaf, sit in a group owned by their parent item.
+ while (view.getChildCount() > 0) {
+ view = view.getChildNode(0);
+ Element group = getClosestRoleAncestor(view.getElement());
+ assertEquals("group", group.getAttribute("role"));
+ assertEquals(view.getParentNode().getElement(), getClosestRoleAncestor(group));
+ }
+ assertTrue(view.isLeaf());
+ }
+
+ public void testExpandImagesHiddenFromAssistiveTech() {
+ CellTree cellTree = (CellTree) tree;
+ TreeNode rootNode = cellTree.getRootTreeNode();
+ // Reading the child count flushes the pending render of the opened node's children.
+ assertTrue(rootNode.setChildOpen(0, true).getChildCount() > 0);
+ CellTreeNodeView> aView = cellTree.rootNode.getChildNode(0);
+ assertEquals("true", aView.getImageElement().getAttribute("aria-hidden"));
+ CellTreeNodeView> aViewChild = aView.getChildNode(0);
+ // A leaf renders a placeholder div instead of the closed image.
+ assertFalse(aViewChild.isLeaf());
+ assertEquals("true", aViewChild.getImageElement().getAttribute("aria-hidden"));
+
+ rootNode.setChildOpen(0, false);
+ assertEquals("true", aView.getImageElement().getAttribute("aria-hidden"));
+
+ Element loading = Document.get().createDivElement();
+ loading.setInnerSafeHtml(cellTree.getLoadingImageHtml());
+ assertEquals("true", loading.getFirstChildElement().getAttribute("aria-hidden"));
+ }
+
+ private static Element getClosestRoleAncestor(Element elem) {
+ Element parent = elem.getParentElement();
+ while (parent != null && parent.getAttribute("role").isEmpty()) {
+ parent = parent.getParentElement();
+ }
+ return parent;
+ }
+
public void testExplicitKeyboardSelection() {
CellTree cellTree = (CellTree) tree;
TreeNode root = cellTree.getRootTreeNode();