-Added supporting library for setting of ARIA roles/states (it is currently just a wrapper around DOM.set/get/removeElementAttribute calls)
-Added ARIA roles/states for CustomButton, Tree, TreeItem, MenuBar, MenuItem, TabBar, TabPanel
-Added keyboard navigation support to TabBar and MenuBar
-Enured that tabindexes are properly set for all widgets that derive from FocusWidget
-Set a tabindex of -1 on GWT History and Script IFRAMES so that they are not part of the tab cycle
 
Patch by: rshearer, rdayal, clchen, jgw, raman

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1977 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/linker/HostedModeTemplate.js b/dev/core/src/com/google/gwt/dev/linker/HostedModeTemplate.js
index 058fe4c..d4d74ad 100644
--- a/dev/core/src/com/google/gwt/dev/linker/HostedModeTemplate.js
+++ b/dev/core/src/com/google/gwt/dev/linker/HostedModeTemplate.js
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -348,6 +348,7 @@
       iframe.src = "javascript:''";
       iframe.id = "__MODULE_NAME__";
       iframe.style.cssText = "position:absolute;width:0;height:0;border:none";
+      iframe.tabIndex = -1;
       // Due to an IE6/7 refresh quirk, this must be an appendChild.
       $doc.body.appendChild(iframe);
       
diff --git a/dev/core/src/com/google/gwt/dev/linker/IFrameTemplate.js b/dev/core/src/com/google/gwt/dev/linker/IFrameTemplate.js
index 39a35e0..0865515 100644
--- a/dev/core/src/com/google/gwt/dev/linker/IFrameTemplate.js
+++ b/dev/core/src/com/google/gwt/dev/linker/IFrameTemplate.js
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -317,6 +317,7 @@
       iframe.src = "javascript:''";
       iframe.id = "__MODULE_NAME__";
       iframe.style.cssText = "position:absolute;width:0;height:0;border:none";
+      iframe.tabIndex = -1;
       // Due to an IE6/7 refresh quirk, this must be an appendChild.
       $doc.body.appendChild(iframe);
       
diff --git a/samples/dynatable/src/com/google/gwt/sample/dynatable/public/DynaTable.html b/samples/dynatable/src/com/google/gwt/sample/dynatable/public/DynaTable.html
index e3cc2c5..cb6e471 100644
--- a/samples/dynatable/src/com/google/gwt/sample/dynatable/public/DynaTable.html
+++ b/samples/dynatable/src/com/google/gwt/sample/dynatable/public/DynaTable.html
@@ -22,7 +22,7 @@
     <title></title>
   </head>
   <body>
-    <iframe src="javascript:''" id='__gwt_historyFrame' style='width:0;height:0;border:0'></iframe>
+  <iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
 	<script type="text/javascript" language='javascript' src='com.google.gwt.sample.dynatable.DynaTable.nocache.js'></script>
     <h1>School Schedule for Professors and Students</h1>
     <table width="100%" border="0" summary="School Schedule for Professors and Students">
diff --git a/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.html b/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.html
index d5d3ef5..6844b7a 100644
--- a/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.html
+++ b/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.html
@@ -22,6 +22,6 @@
 	</head>
 	<body>
 		<script type="text/javascript" language='javascript' src='com.google.gwt.sample.kitchensink.KitchenSink.nocache.js'></script>
-		<iframe src="javascript:''" id='__gwt_historyFrame' style='width:0;height:0;border:0'></iframe>
+		<iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
 	</body>
 </html>
diff --git a/samples/simplerpc/src/com/google/gwt/sample/simplerpc/public/SimpleRPC.html b/samples/simplerpc/src/com/google/gwt/sample/simplerpc/public/SimpleRPC.html
index 406f66b..e7f5be8 100644
--- a/samples/simplerpc/src/com/google/gwt/sample/simplerpc/public/SimpleRPC.html
+++ b/samples/simplerpc/src/com/google/gwt/sample/simplerpc/public/SimpleRPC.html
@@ -22,7 +22,7 @@
 		<title>SimpleRPC</title>
 	</head>
 	<body>
-		<iframe src="javascript:''" id='__gwt_historyFrame' style='width:0;height:0;border:0'></iframe>
+		<iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
 		<script type="text/javascript" language='javascript' src='com.google.gwt.sample.simplerpc.SimpleRPC.nocache.js'>
     </script>
 		<h1> Simple RPC</h1>
diff --git a/user/src/com/google/gwt/core/public/history.html b/user/src/com/google/gwt/core/public/history.html
index 4ba2dce..b06f46a 100644
--- a/user/src/com/google/gwt/core/public/history.html
+++ b/user/src/com/google/gwt/core/public/history.html
@@ -15,7 +15,7 @@
 </script></head>
 <body onload='hst()'>
 
-<input type='text' id='__gwt_historyToken'>
+<input type='text' id='__gwt_historyToken' tabIndex='-1'>
 
 </body>
 </html>
diff --git a/user/src/com/google/gwt/user/Accessibility.gwt.xml b/user/src/com/google/gwt/user/Accessibility.gwt.xml
new file mode 100644
index 0000000..92f153f
--- /dev/null
+++ b/user/src/com/google/gwt/user/Accessibility.gwt.xml
@@ -0,0 +1,32 @@
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- may obtain a copy of the License at                                    -->
+<!--                                                                        -->
+<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
+<!--                                                                        -->
+<!-- Unless required by applicable law or agreed to in writing, software    -->
+<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
+<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
+<!-- implied. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- Deferred binding rules for browser selection.                          -->
+<!--                                                                        -->
+<!-- This module is typically inherited via com.google.gwt.user.User        -->
+<!--                                                                        -->
+<module>
+	<inherits name="com.google.gwt.core.Core" />
+	<inherits name="com.google.gwt.user.UserAgent" />
+
+	<!-- Mozilla has a different implementation because of support for ARIA -->
+	<replace-with
+		class="com.google.gwt.user.client.ui.impl.AccessibilityImplMozilla">
+		<when-type-is
+			class="com.google.gwt.user.client.ui.impl.AccessibilityImpl" />
+		<any>
+			<when-property-is name="user.agent" value="gecko1_8" />
+		</any>
+	</replace-with>
+</module>
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 4110e74..d5559fe 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -35,4 +35,5 @@
    <inherits name="com.google.gwt.user.SplitPanel"/>
    <inherits name="com.google.gwt.user.ListBox" />
    <inherits name="com.google.gwt.user.TitledPanel" />
+   <inherits name="com.google.gwt.user.Accessibility"/>
 </module>
diff --git a/user/src/com/google/gwt/user/client/DOM.java b/user/src/com/google/gwt/user/client/DOM.java
index 28e724d..830f8b8 100644
--- a/user/src/com/google/gwt/user/client/DOM.java
+++ b/user/src/com/google/gwt/user/client/DOM.java
@@ -33,6 +33,8 @@
   private static Event currentEvent = null;
   private static final DOMImpl impl = GWT.create(DOMImpl.class);
   private static Element sCaptureElem;
+  // Used to generate unique DOM ids.
+  private static int nextDOMId = 0;
 
   // <BrowserEventPreview>
   private static ArrayList<EventPreview> sEventPreviewStack;
@@ -358,6 +360,15 @@
   }
 
   /**
+   * Generates a unique DOM id. The id is of the form "gwt-id-<unique integer>".
+   *
+   * @return a unique DOM id
+   */
+  public static String createUniqueId() {
+    return "gwt-id-" + nextDOMId++;
+  }
+
+  /**
    * Cancels bubbling for the given event. This will stop the event from being
    * propagated to parent elements.
    * 
diff --git a/user/src/com/google/gwt/user/client/ui/Accessibility.java b/user/src/com/google/gwt/user/client/ui/Accessibility.java
new file mode 100644
index 0000000..66bb5ee
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/Accessibility.java
@@ -0,0 +1,119 @@
+/*

+ * Copyright 2008 Google Inc.

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ *

+ * http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package com.google.gwt.user.client.ui;

+

+import com.google.gwt.core.client.GWT;

+import com.google.gwt.user.client.ui.impl.AccessibilityImpl;

+import com.google.gwt.user.client.Element;

+

+/**

+ * Allows ARIA attributes to be added to widgets so that they can be

+ * identified by assistive technologies. FireFox 3 is the only browser that

+ * currently supports this feature. However, in the future, a new version of

+ * FireVox will be created that will support this implementation and work with

+ * FireFox 2.

+ *

+ * A 'role' describes the role a widget plays in a page: i.e. a checkbox widget

+ * is assigned a "checkbox" role.

+ *

+ * A 'state' describes the current state of the widget. For example, a checkbox

+ * widget has the state "checked", which is given a value of "true" or "false"

+ * depending on whether it is currently checked or unchecked.

+ *

+ * See {@see <a href="http://developer.mozilla.org/en/docs/Accessible_DHTML">http://developer.mozilla.org/en/docs/Accessible_DHTML</a>}

+ * for more information.

+ *

+ * Note that this API is package protected. At this time, the ARIA specification is still

+ * in flux, which means that this API is subject to change. Once we are fairly confident

+ * that this API will remain stable, we will make it public.

+ */

+

+final class Accessibility {

+

+  public static final String ROLE_TREE = "tree";

+  public static final String ROLE_TREEITEM = "treeitem";

+  public static final String ROLE_BUTTON = "button";

+  public static final String ROLE_TABLIST = "tablist";

+  public static final String ROLE_TAB = "tab";

+  public static final String ROLE_TABPANEL = "tabpanel";

+  public static final String ROLE_MENUBAR = "menubar";

+  public static final String ROLE_MENUITEM = "menuitem";

+

+  public static final String STATE_ACTIVEDESCENDANT = "aria-activedescendant";

+  public static final String STATE_POSINSET = "aria-posinset";

+  public static final String STATE_SETSIZE = "aria-setsize";

+  public static final String STATE_SELECTED = "aria-selected";

+  public static final String STATE_EXPANDED = "aria-expanded";

+  public static final String STATE_LEVEL = "aria-level";

+  public static final String STATE_HASPOPUP = "aria-haspopup";

+

+  private static AccessibilityImpl impl = (AccessibilityImpl) GWT

+      .create(AccessibilityImpl.class);

+

+  /**

+   * Requests the string value of the role with the specified namespace.

+   *

+   * @param elem the element which has the specified role

+   * @return the value of the role, or an empty string if none exists

+   */

+  public static String getRole(Element elem) {

+    return impl.getRole(elem);

+  }

+

+  /**

+   * Requests the string value of the state with the specified namespace.

+   *

+   * @param elem the element which has the specified state

+   * @param stateName the name of the state

+   * @return the value of the state, or an empty string if none exists

+   */

+  public static String getState(Element elem, String stateName) {

+    return impl.getState(elem, stateName);

+  }

+

+  /**

+   * Removes the state from the given element.

+   *

+   * @param elem the element which has the specified state

+   * @param stateName the name of the state to remove

+   */

+  public static void removeState(Element elem, String stateName)  {

+    impl.removeState(elem, stateName);

+  }

+  /**

+   * Assigns the specified element the specified role and value for that role.

+   *

+   * @param elem the element to be given the specified role

+   * @param roleName the name of the role

+   */

+  public static void setRole(Element elem, String roleName) {

+    impl.setRole(elem, roleName);

+  }

+

+  /**

+   * Assigns the specified element the specified state and value for that state.

+   *

+   * @param elem the element to be given the specified state

+   * @param stateName the name of the state

+   * @param stateValue the value of the state

+   */

+  public static void setState(Element elem, String stateName, String stateValue) {

+    impl.setState(elem, stateName, stateValue);

+  }

+

+  private Accessibility() {

+  }

+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/client/ui/CheckBox.java b/user/src/com/google/gwt/user/client/ui/CheckBox.java
index 82a603c..6ef4f64 100644
--- a/user/src/com/google/gwt/user/client/ui/CheckBox.java
+++ b/user/src/com/google/gwt/user/client/ui/CheckBox.java
@@ -35,7 +35,6 @@
  * </p>
  */
 public class CheckBox extends ButtonBase implements HasName {
-  private static int uniqueId;
   private Element inputElem, labelElem;
 
   /**
@@ -79,9 +78,16 @@
     DOM.appendChild(getElement(), inputElem);
     DOM.appendChild(getElement(), labelElem);
 
-    String uid = "check" + (++uniqueId);
+    String uid = DOM.createUniqueId();
     DOM.setElementProperty(inputElem, "id", uid);
     DOM.setElementProperty(labelElem, "htmlFor", uid);
+
+    // Accessibility: setting tab index to be 0 by default, ensuring element
+    // appears in tab sequence. FocusWidget's setElement method already
+    // calls setTabIndex, which is overridden below. However, at the time
+    // that this call is made, inputElem has not been created. So, we have
+    // to call setTabIndex again, once inputElem has been created. 
+    setTabIndex(0);
   }
 
   @Override
@@ -163,7 +169,13 @@
 
   @Override
   public void setTabIndex(int index) {
-    getFocusImpl().setTabIndex(inputElem, index);
+    // Need to guard against call to setTabIndex before inputElem is initialized.
+    // This happens because FocusWidget's (a superclass of CheckBox) setElement method
+    // calls setTabIndex before inputElem is initialized. See CheckBox's protected
+    // constructor for more information.
+    if (inputElem != null) {
+      getFocusImpl().setTabIndex(inputElem, index);
+    }
   }
 
   @Override
@@ -247,7 +259,7 @@
     // Setup the new element
     DOM.sinkEvents(inputElem, sunkEvents);
     DOM.setElementProperty(inputElem, "id", uid);
-    if (accessKey != "") {
+    if (!accessKey.equals("")) {
       DOM.setElementProperty(inputElem, "accessKey", accessKey);
     }
     setTabIndex(tabIndex);
diff --git a/user/src/com/google/gwt/user/client/ui/CustomButton.java b/user/src/com/google/gwt/user/client/ui/CustomButton.java
index 6810922..87da861 100644
--- a/user/src/com/google/gwt/user/client/ui/CustomButton.java
+++ b/user/src/com/google/gwt/user/client/ui/CustomButton.java
@@ -411,6 +411,9 @@
     sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS);
     setUpFace(createFace(null, "up", UP));
     setStyleName(STYLENAME_DEFAULT);
+
+    // Add a11y role "button"
+    Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/ui/FocusWidget.java b/user/src/com/google/gwt/user/client/ui/FocusWidget.java
index ebbf56b..e1ea806 100644
--- a/user/src/com/google/gwt/user/client/ui/FocusWidget.java
+++ b/user/src/com/google/gwt/user/client/ui/FocusWidget.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -164,6 +164,18 @@
     impl.setTabIndex(getElement(), index);
   }
 
+  @Override
+  protected void setElement(Element elem) {
+    super.setElement(elem);
+
+    // Accessibility: setting tab index to be 0 by default, ensuring element
+    // appears in tab sequence. Note that this call will not interfere with
+    // any calls made to FocusWidget.setTabIndex(int) by user code, because
+    // FocusWidget.setTabIndex(int) cannot be called until setElement(elem)
+    // has been called.
+    setTabIndex(0);
+  }
+
   /**
    * Fire all current {@link ClickListener}.
    */
diff --git a/user/src/com/google/gwt/user/client/ui/HTMLPanel.java b/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
index 323ed2e..fb7e18e 100644
--- a/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
@@ -26,8 +26,6 @@
  */
 public class HTMLPanel extends ComplexPanel {
 
-  private static int sUid;
-
   /**
    * A helper method for creating unique IDs for elements within dynamically-
    * generated HTML. This is important because no two elements in a document
@@ -36,7 +34,7 @@
    * @return a new unique identifier
    */
   public static String createUniqueId() {
-    return "HTMLPanel_" + (++sUid);
+    return DOM.createUniqueId();
   }
 
   private Element hiddenDiv;
diff --git a/user/src/com/google/gwt/user/client/ui/MenuBar.java b/user/src/com/google/gwt/user/client/ui/MenuBar.java
index cf1df6e..757f44e 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuBar.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -97,11 +97,15 @@
 
     this.vertical = vertical;
 
-    Element outer = DOM.createDiv();
+    Element outer = FocusPanel.impl.createFocusable();
     DOM.appendChild(outer, table);
     setElement(outer);
 
-    sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
+    Accessibility.setRole(getElement(), Accessibility.ROLE_MENUBAR);
+
+    sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
+        | Event.ONFOCUS |  Event.ONKEYDOWN);
+
     setStyleName("gwt-MenuBar");
     if (vertical) {
       addStyleDependentName("vertical");
@@ -239,6 +243,7 @@
     MenuItem item = findItem(DOM.eventGetTarget(event));
     switch (DOM.eventGetType(event)) {
       case Event.ONCLICK: {
+        FocusPanel.impl.focus(getElement());  
         // Fire an item's command when the user clicks on it.
         if (item != null) {
           doItemAction(item, true);
@@ -259,7 +264,40 @@
         }
         break;
       }
-    }
+
+      case Event.ONFOCUS: {
+        selectFirstItemIfNoneSelected();
+        break;
+      }
+
+      case Event.ONKEYDOWN: {
+        int keyCode = DOM.eventGetKeyCode(event);
+        switch (keyCode) {
+          case KeyboardListener.KEY_LEFT:
+            moveLeft();
+            break;
+          case KeyboardListener.KEY_RIGHT:
+            moveRight();
+            break;
+          case KeyboardListener.KEY_UP:
+            moveUp();
+            break;
+          case KeyboardListener.KEY_DOWN:
+            moveDown();
+            break;
+          case KeyboardListener.KEY_ESCAPE:
+            closeAllParents();
+            break;
+          case KeyboardListener.KEY_ENTER:
+            if (!selectFirstItemIfNoneSelected()) {
+              doItemAction(selectedItem, true);
+            }
+            break;
+        } // end switch(keyCode)
+
+        break;
+      } // end case Event.ONKEYDOWN
+    } // end switch (DOM.eventGetType(event))
   }
 
   public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
@@ -361,14 +399,8 @@
    */
   void closeAllParents() {
     MenuBar curMenu = this;
-    while (curMenu != null) {
+    while (curMenu.parentMenu != null) {
       curMenu.close();
-
-      if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) {
-        curMenu.selectedItem.setSelectionStyle(false);
-        curMenu.selectedItem = null;
-      }
-
       curMenu = curMenu.parentMenu;
     }
   }
@@ -395,7 +427,7 @@
     }
 
     // If the item has no popup, optionally fire its command.
-    if (item.getSubMenu() == null) {
+    if ((item != null) && (item.getSubMenu() == null)) {
       if (fireCommand) {
         // Close this menu and all of its parents.
         closeAllParents();
@@ -412,6 +444,10 @@
     // Ensure that the item is selected.
     selectItem(item);
 
+    if (item == null) {
+      return;
+    }
+    
     // Create a new popup for this item, and position it next to
     // the item (below if this is a horizontal menu bar, to the
     // right if it's a vertical bar).
@@ -457,6 +493,7 @@
     // Show the popup, ensuring that the menubar's event preview remains on top
     // of the popup's.
     popup.show();
+    shownChildMenu.focus();
   }
 
   void itemOver(MenuItem item) {
@@ -492,6 +529,9 @@
 
     if (item != null) {
       item.setSelectionStyle(true);
+
+      Accessibility.setState(getElement(), Accessibility.STATE_ACTIVEDESCENDANT,
+          DOM.getElementAttribute(item.getElement(), "id"));
     }
 
     selectedItem = item;
@@ -537,16 +577,18 @@
   }
 
   private MenuItem findItem(Element hItem) {
-    for (int i = 0; i < items.size(); ++i) {
-      MenuItem item = items.get(i);
+    for (MenuItem item : items) {
       if (DOM.isOrHasChild(item.getElement(), hItem)) {
         return item;
       }
     }
-
     return null;
   }
 
+  private void focus() {
+    FocusPanel.impl.focus(getElement());
+  }
+
   private Element getItemContainerElement() {
     if (vertical) {
       return body;
@@ -555,6 +597,76 @@
     }
   }
 
+  private void moveDown() {
+    if (selectFirstItemIfNoneSelected()) {
+      return;
+    }
+
+    if (vertical) {
+      selectNextItem();
+    } else {
+      if (selectedItem.getSubMenu() != null) {
+        doItemAction(selectedItem, false);
+      } else if (parentMenu != null) {
+        if (parentMenu.vertical) {
+          parentMenu.selectNextItem();
+        } else {
+          parentMenu.moveDown();
+        }
+      }
+    }
+  }
+
+  private void moveLeft() {
+    if (selectFirstItemIfNoneSelected()) {
+      return;
+    }
+
+    if (!vertical) {
+      selectPrevItem();
+    } else {
+      if ((parentMenu != null) && (!parentMenu.vertical)) {
+        parentMenu.selectPrevItem();
+      } else {
+        close();
+      }
+    }
+  }
+
+  private void moveRight() {
+    if (selectFirstItemIfNoneSelected()) {
+      return;
+    }
+
+    if (!vertical) {
+      selectNextItem();
+    } else {
+      if ((shownChildMenu == null) && (selectedItem.getSubMenu() != null)) {
+        doItemAction(selectedItem, false);
+      } else if (parentMenu != null) {
+        if (!parentMenu.vertical) {
+          parentMenu.selectNextItem();
+        } else {
+          parentMenu.moveRight();
+        }
+      }
+    }
+  }
+
+  private void moveUp() {
+    if (selectFirstItemIfNoneSelected()) {
+      return;
+    }
+
+    if ((shownChildMenu == null) && vertical) {
+      selectPrevItem();
+    } else if ((parentMenu != null) && parentMenu.vertical) {
+      parentMenu.selectPrevItem();
+    } else {
+      close();
+    }
+  }
+
   /*
    * This method is called when a menu bar is hidden, so that it can hide any
    * child popups that are currently being shown.
@@ -563,6 +675,7 @@
     if (shownChildMenu != null) {
       shownChildMenu.onHide();
       popup.hide();
+      focus();
     }
   }
 
@@ -594,4 +707,69 @@
     allItems.remove(idx);
     return true;
   }
+
+  /**
+   * Selects the first item in the menu if no items are currently selected. This method
+   * assumes that the menu has at least 1 item.
+   *
+   * @return true if no item was previously selected and the first item in the list was selected,
+   *         false otherwise
+   */
+  private boolean selectFirstItemIfNoneSelected() {
+    if (selectedItem == null) {
+      MenuItem nextItem = items.get(0);
+      selectItem(nextItem);
+      return true;
+    }
+
+    return false;
+  }
+
+  private void selectNextItem() {
+    if (selectedItem == null) {
+      return;
+    }
+
+    int index = items.indexOf(selectedItem);
+    // We know that selectedItem is set to an item that is contained in the items collection.
+    // Therefore, we know that index can never be -1.
+    assert (index != -1);
+
+    MenuItem itemToBeSelected;
+
+    if (index < items.size() - 1) {
+      itemToBeSelected = items.get(index + 1);
+    } else { // we're at the end, loop around to the start
+      itemToBeSelected = items.get(0);
+    }
+
+    selectItem(itemToBeSelected);
+    if (shownChildMenu != null) {
+      doItemAction(itemToBeSelected, false);
+    }
+  }
+
+  private void selectPrevItem() {
+    if (selectedItem == null) {
+      return;
+    }
+
+    int index = items.indexOf(selectedItem);
+    // We know that selectedItem is set to an item that is contained in the items collection.
+    // Therefore, we know that index can never be -1.
+    assert (index != -1);
+
+    MenuItem itemToBeSelected;
+    if (index > 0) {
+      itemToBeSelected = items.get(index - 1);
+
+    } else { // we're at the start, loop around to the end
+      itemToBeSelected = items.get(items.size() - 1);
+    }
+    
+    selectItem(itemToBeSelected);
+    if (shownChildMenu != null) {
+      doItemAction(itemToBeSelected, false);
+    }
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/MenuItem.java b/user/src/com/google/gwt/user/client/ui/MenuItem.java
index e03ae76..d201f30 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuItem.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuItem.java
@@ -23,6 +23,9 @@
  * {@link com.google.gwt.user.client.ui.MenuBar}. Menu items can either fire a
  * {@link com.google.gwt.user.client.Command} when they are clicked, or open a
  * cascading sub-menu.
+ *
+ * Each menu item is assigned a unique DOM id in order to support ARIA. See
+ * {@link com.google.gwt.user.client.ui.Accessibility} for more information.
  */
 public class MenuItem extends UIObject implements HasHTML {
 
@@ -87,6 +90,10 @@
       setText(text);
     }
     setStyleName("gwt-MenuItem");
+
+    DOM.setElementAttribute(getElement(), "id", DOM.createUniqueId());
+    // Add a11y role "menuitem"
+    Accessibility.setRole(getElement(), Accessibility.ROLE_MENUITEM);
   }
 
   /**
@@ -144,6 +151,13 @@
    */
   public void setSubMenu(MenuBar subMenu) {
     this.subMenu = subMenu;
+
+    // Change tab index from 0 to -1, because only the root menu is supposed to
+    // be in the tab order
+    FocusPanel.impl.setTabIndex(subMenu.getElement(), -1);
+
+    // Update a11y role "haspopup"
+    Accessibility.setState(this.getElement(), Accessibility.STATE_HASPOPUP, "true");
   }
 
   public void setText(String text) {
diff --git a/user/src/com/google/gwt/user/client/ui/TabBar.java b/user/src/com/google/gwt/user/client/ui/TabBar.java
index 4d1a90d..2477531 100644
--- a/user/src/com/google/gwt/user/client/ui/TabBar.java
+++ b/user/src/com/google/gwt/user/client/ui/TabBar.java
@@ -43,7 +43,7 @@
  * </p>
  */
 public class TabBar extends Composite implements SourcesTabEvents,
-    ClickListener {
+    ClickListener, KeyboardListener {
 
   /**
    * <code>ClickDecoratorPanel</code> decorates any widget with the minimal
@@ -52,20 +52,34 @@
    * single observer is needed.
    */
   private static final class ClickDecoratorPanel extends SimplePanel {
-    ClickListener delegate;
+    ClickListener clickDelegate;
+    KeyboardListener keyDelegate;
 
-    ClickDecoratorPanel(Widget child, ClickListener delegate) {
-      this.delegate = delegate;
+    ClickDecoratorPanel(Widget child, ClickListener cDelegate,
+        KeyboardListener kDelegate) {
+
+      // The panel needs to be able to get keyboard focus for tab navigation
+      super(FocusPanel.impl.createFocusable());
+
+      this.clickDelegate = cDelegate;
+      this.keyDelegate = kDelegate;
       setWidget(child);
-      sinkEvents(Event.ONCLICK);
+      sinkEvents(Event.ONCLICK | Event.ONKEYDOWN);
     }
 
     @Override
     public void onBrowserEvent(Event event) {
       // No need for call to super.
       switch (DOM.eventGetType(event)) {
+
         case Event.ONCLICK:
-          delegate.onClick(this);
+          clickDelegate.onClick(this);
+          break;
+
+        case Event.ONKEYDOWN:
+          keyDelegate.onKeyDown(this, ((char) DOM.eventGetKeyCode(event)),
+              KeyboardListenerCollection.getKeyboardModifiers(event));
+          break;
       }
     }
   }
@@ -83,6 +97,9 @@
     sinkEvents(Event.ONCLICK);
     setStyleName("gwt-TabBar");
 
+    // Add a11y role "tablist"
+    Accessibility.setRole(panel.getElement(), Accessibility.ROLE_TABLIST);
+
     panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);
 
     HTML first = new HTML("&nbsp;", true), rest = new HTML("&nbsp;", true);
@@ -192,12 +209,8 @@
       item = new Label(text);
     }
 
-    item.setWordWrap(false);
-    item.addClickListener(this);
-    item.setStyleName(STYLENAME_DEFAULT);
-    panel.insert(item, beforeIndex + 1);
-    setStyleName(DOM.getParent(item.getElement()), STYLENAME_DEFAULT
-        + "-wrapper", true);
+    item.setWordWrap(false);    
+    insertTabImpl(item, beforeIndex);
   }
 
   /**
@@ -218,23 +231,25 @@
    */
   public void insertTab(Widget widget, int beforeIndex) {
     checkInsertBeforeTabIndex(beforeIndex);
-
-    ClickDecoratorPanel decWidget = new ClickDecoratorPanel(widget, this);
-    decWidget.addStyleName(STYLENAME_DEFAULT);
-    panel.insert(decWidget, beforeIndex + 1);
-    setStyleName(DOM.getParent(decWidget.getElement()), STYLENAME_DEFAULT
-        + "-wrapper", true);
+    insertTabImpl(widget, beforeIndex);  
   }
 
   public void onClick(Widget sender) {
-    for (int i = 1; i < panel.getWidgetCount() - 1; ++i) {
-      if (panel.getWidget(i) == sender) {
-        selectTab(i - 1);
-        return;
-      }
+    selectTabByTabWidget(sender);
+  }
+
+  public void onKeyDown(Widget sender, char keyCode, int modifiers) {
+    if (keyCode == KeyboardListener.KEY_ENTER) {
+      selectTabByTabWidget(sender);
     }
   }
 
+  public void onKeyPress(Widget sender, char keyCode, int modifiers) {
+  }
+
+  public void onKeyUp(Widget sender, char keyCode, int modifiers) {
+  }
+
   /**
    * Removes the tab at the specified index.
    * 
@@ -323,6 +338,38 @@
     }
   }
 
+  private void insertTabImpl(Widget widget, int beforeIndex) {
+    ClickDecoratorPanel decWidget = new ClickDecoratorPanel(widget, this, this);
+    decWidget.addStyleName(STYLENAME_DEFAULT);
+    // Add a11y role "tab"
+    Accessibility.setRole(decWidget.getElement(), Accessibility.ROLE_TAB);
+
+    panel.insert(decWidget, beforeIndex + 1);
+    setStyleName(DOM.getParent(decWidget.getElement()), STYLENAME_DEFAULT
+        + "-wrapper", true);
+  }
+
+  /**
+   * Selects the tab corresponding to the widget for the tab. To be clear
+   * the widget for the tab is not the widget INSIDE of the tab; it is the
+   * widget used to represent the tab itself.
+   *
+   * @param tabWidget The widget for the tab to be selected
+   * @return true if the tab corresponding to the widget for the tab could located and selected,
+   *         false otherwise
+   */
+  private boolean selectTabByTabWidget(Widget tabWidget) {
+    int numTabs = panel.getWidgetCount() - 1;
+
+    for (int i = 1; i < numTabs; ++i) {
+      if (panel.getWidget(i) == tabWidget) {
+        return selectTab(i - 1);
+      }
+    }
+
+    return false;
+  }
+   
   private void setSelectionStyle(Widget item, boolean selected) {
     if (item != null) {
       if (selected) {
diff --git a/user/src/com/google/gwt/user/client/ui/TabPanel.java b/user/src/com/google/gwt/user/client/ui/TabPanel.java
index 4fa2c43..b4bf75d 100644
--- a/user/src/com/google/gwt/user/client/ui/TabPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/TabPanel.java
@@ -193,6 +193,8 @@
     initWidget(panel);
     setStyleName("gwt-TabPanel");
     deck.setStyleName("gwt-TabPanelBottom");
+    // Add a11y role "tabpanel"
+    Accessibility.setRole(deck.getElement(), Accessibility.ROLE_TABPANEL);
   }
 
   public void add(Widget w) {
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index 6fdc56a..a62a5fc 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -57,7 +57,7 @@
   private static class ImagesFromImageBase implements TreeImages {
 
     /**
-     * A convience image prototype that implements
+     * A convenience image prototype that implements
      * {@link AbstractImagePrototype#applyTo(Image)} for a specified image name.
      */
     private class Prototype extends AbstractImagePrototype {
@@ -110,6 +110,16 @@
     }
   }
 
+  static native boolean shouldTreeDelegateFocusToElement(Element elem) /*-{
+     var name = elem.nodeName;
+     return ((name == "SELECT") ||
+         (name == "INPUT")  ||
+         (name == "TEXTAREA") ||
+         (name == "OPTION") ||
+         (name == "BUTTON") ||
+         (name == "LABEL"));
+   }-*/;
+
   /**
    * Map of TreeItem.widget -> TreeItem.
    */
@@ -191,6 +201,10 @@
     };
     root.setTree(this);
     setStyleName("gwt-Tree");
+    
+    // Add a11y role "tree"
+    Accessibility.setRole(getElement(), Accessibility.ROLE_TREE);
+    Accessibility.setRole(focusable, Accessibility.ROLE_TREEITEM);
   }
 
   /**
@@ -365,7 +379,7 @@
         // to be sunk on individual items' open/close images. This leads to an
         // extra event reaching the Tree, which we will ignore here.
         if (DOM.eventGetCurrentTarget(event).equals(getElement())) {
-          elementClicked(root, DOM.eventGetTarget(event));
+          elementClicked(DOM.eventGetTarget(event));
         }
         break;
       }
@@ -399,7 +413,6 @@
       }
 
       case Event.ONFOCUS:
-        // If we already have focus, ignore the focus event.
         if (focusListeners != null) {
           focusListeners.fireFocusEvent(this, event);
         }
@@ -713,7 +726,7 @@
     chain.add(hElem);
   }
 
-  private boolean elementClicked(TreeItem root, Element hElem) {
+  private boolean elementClicked(Element hElem) {
     ArrayList<Element> chain = new ArrayList<Element>();
     collectElementChain(chain, getElement(), hElem);
 
@@ -759,19 +772,17 @@
   }
 
   /**
-   * Move the tree focus to the specified selected item.
-   * 
-   * @param selection
+   * Move the tree focus to the currently selected item.
    */
-  private void moveFocus(TreeItem selection) {
-    HasFocus focusableWidget = selection.getFocusableWidget();
+  private void moveFocus() {
+    HasFocus focusableWidget = curSelection.getFocusableWidget();
     if (focusableWidget != null) {
       focusableWidget.setFocus(true);
       DOM.scrollIntoView(((Widget) focusableWidget).getElement());
     } else {
       // Get the location and size of the given item's content element relative
       // to the tree.
-      Element selectedElem = selection.getContentElem();
+      Element selectedElem = curSelection.getContentElem();
       int containerLeft = getAbsoluteLeft();
       int containerTop = getAbsoluteTop();
 
@@ -790,9 +801,12 @@
       // Scroll it into view.
       DOM.scrollIntoView(focusable);
 
+      // Update ARIA attributes to reflect the information from the newly-selected item.
+      updateAriaAttributes();
+      
       // Ensure Focus is set, as focus may have been previously delegated by
       // tree.
-      FocusPanel.impl.focus(focusable);
+      setFocus(true);
     }
   }
 
@@ -853,7 +867,7 @@
     curSelection = item;
 
     if (moveFocus && curSelection != null) {
-      moveFocus(curSelection);
+      moveFocus();
 
       // Select the item and fire the selection event.
       curSelection.setSelected(true);
@@ -863,13 +877,67 @@
     }
   }
 
-  private native boolean shouldTreeDelegateFocusToElement(Element elem) /*-{
-    var name = elem.nodeName;
-    return ((name == "SELECT") ||
-        (name == "INPUT")  ||
-        (name == "TEXTAREA") ||
-        (name == "OPTION") ||
-        (name == "BUTTON") ||
-        (name == "LABEL"));
-  }-*/;
+  private void updateAriaAttributes() {
+
+    Element curSelectionContentElem = curSelection.getContentElem();
+
+    // Set the 'aria-level' state. To do this, we need to compute the level of the
+    // currently selected item.
+
+    // We initialize itemLevel to -1 because the level value is zero-based.
+    // Note that the root node is not a part of the TreeItem hierachy, and we
+    // do not consider the root node to have a designated level. The level of
+    // the root's children is level 0, its children's children is level 1, etc.
+
+    int curSelectionLevel = -1;
+    TreeItem tempItem = curSelection;
+
+    while (tempItem != null) {
+      tempItem = tempItem.getParentItem();
+      ++curSelectionLevel;
+    }
+
+    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_LEVEL,
+        String.valueOf(curSelectionLevel + 1));
+
+    // Set the 'aria-setsize' and 'aria-posinset' states. To do this, we need to
+    // compute the the number of siblings that the currently selected item has, and
+    // the item's position among its siblings.
+
+    TreeItem curSelectionParent = curSelection.getParentItem();
+    if (curSelectionParent == null) {
+      curSelectionParent = root;
+    }
+
+    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_SETSIZE,
+        String.valueOf(curSelectionParent.getChildCount()));
+
+    int curSelectionIndex = curSelectionParent.getChildIndex(curSelection);
+
+    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_POSINSET,
+        String.valueOf(curSelectionIndex + 1));
+
+    // Set the 'aria-expanded' state. This depends on the state of the currently selected item.
+    // If the item has no children, we remove the 'aria-expanded' state.
+
+    if (curSelection.getChildCount() == 0) {
+      Accessibility.removeState(curSelectionContentElem, Accessibility.STATE_EXPANDED);
+    } else {
+      if (curSelection.getState()) {
+        Accessibility.setState(curSelectionContentElem, Accessibility.STATE_EXPANDED, "true");
+      } else {
+        Accessibility.setState(curSelectionContentElem, Accessibility.STATE_EXPANDED, "false");
+      }
+    }
+
+    // Make sure that 'aria-selected' is true.
+
+    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_SELECTED, "true");
+
+    // Update the 'aria-activedescendant' state for the focusable element to match the id
+    // of the currently selected item
+
+    Accessibility.setState(focusable, Accessibility.STATE_ACTIVEDESCENDANT,
+        DOM.getElementAttribute(curSelectionContentElem, "id"));
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index cbe236d..45f6bee 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -24,6 +24,10 @@
 /**
  * An item that can be contained within a
  * {@link com.google.gwt.user.client.ui.Tree}.
+ *
+ * Each tree item is assigned a unique DOM id in order to support ARIA. See
+ * {@link com.google.gwt.user.client.ui.Accessibility} for more information.
+ *
  * <p>
  * <h3>Example</h3>
  * {@example com.google.gwt.examples.TreeExample}
@@ -79,6 +83,9 @@
     DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap");
     DOM.setStyleAttribute(childSpanElem, "whiteSpace", "nowrap");
     setStyleName(contentElem, "gwt-TreeItem", true);
+
+    Accessibility.setRole(contentElem, Accessibility.ROLE_TREEITEM);
+    DOM.setElementAttribute(contentElem, "id", DOM.createUniqueId());
   }
 
   /**
@@ -115,10 +122,9 @@
 
   /**
    * Adds another item as a child to this one.
-   * 
+   *
    * @param item the item to be added
    */
-
   public void addItem(TreeItem item) {
     // Detach item from existing parent.
     if ((item.getParentItem() != null) || (item.getTree() != null)) {
@@ -394,6 +400,13 @@
       if (tree != null) {
         tree.adopt(widget, this);
       }
+
+      // Set tabIndex on the widget to -1, so that it doesn't mess up the tab
+      // order of the entire tree
+
+      if (Tree.shouldTreeDelegateFocusToElement(widget.getElement())) {
+        DOM.setElementAttribute(widget.getElement(), "tabIndex", "-1");
+      }
     }
   }
 
@@ -458,7 +471,7 @@
   Element getImageElement() {
     return statusImage.getElement();
   }
- 
+
   void setParentItem(TreeItem parent) {
     this.parent = parent;
   }
diff --git a/user/src/com/google/gwt/user/client/ui/impl/AccessibilityImpl.java b/user/src/com/google/gwt/user/client/ui/impl/AccessibilityImpl.java
new file mode 100644
index 0000000..90a9b55
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/AccessibilityImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.Element;
+
+/**
+ * Native implementation class used with
+ * {@link com.google.gwt.user.client.ui.Accessibility}.
+ */
+public class AccessibilityImpl {
+
+  public String getRole(Element elem) {
+    return "";
+  }
+
+  public String getState(Element elem, String stateName) {
+    return "";
+  }
+
+  public void removeState(Element elem, String stateName) {    
+  }
+
+  public void setRole(Element elem, String roleName) {
+  }
+
+  public void setState(Element elem, String stateName, String stateValue) {
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/AccessibilityImplMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/AccessibilityImplMozilla.java
new file mode 100644
index 0000000..c3ab5f5
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/AccessibilityImplMozilla.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.DOM;
+
+/**
+ * Firefox 1.5+ implementation of {@link AccessibilityImpl}.
+ */
+public class AccessibilityImplMozilla extends AccessibilityImpl {
+
+  private static final String ATTR_NAME_ROLE = "role";
+
+  public String getRole(Element elem) {
+    return DOM.getElementAttribute(elem, ATTR_NAME_ROLE);
+  }
+
+  public String getState(Element elem, String stateName) {
+    return DOM.getElementAttribute(elem, stateName);
+  }
+
+  public void removeState(Element elem, String stateName) {
+    DOM.removeElementAttribute(elem, stateName);
+  }
+  
+  public void setRole(Element elem, String roleName) {
+    DOM.setElementAttribute(elem, ATTR_NAME_ROLE, roleName);
+  }
+
+  public void setState(Element elem, String stateName, String stateValue) {
+    DOM.setElementAttribute(elem, stateName, stateValue);
+  }
+}
diff --git a/user/src/com/google/gwt/user/tools/AppHtml.htmlsrc b/user/src/com/google/gwt/user/tools/AppHtml.htmlsrc
index c92da8d..269f56c 100644
--- a/user/src/com/google/gwt/user/tools/AppHtml.htmlsrc
+++ b/user/src/com/google/gwt/user/tools/AppHtml.htmlsrc
@@ -46,7 +46,7 @@
   <body>
 
     <!-- OPTIONAL: include this if you want history support -->
-    <iframe src="javascript:''" id="__gwt_historyFrame" style="position:absolute;width:0;height:0;border:0"></iframe>
+    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
 
     <h1>@className</h1>