Support ARIA "tab" roles
authorcfleizach@apple.com <cfleizach@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Nov 2009 16:57:47 +0000 (16:57 +0000)
committercfleizach@apple.com <cfleizach@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Nov 2009 16:57:47 +0000 (16:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=30842

Reviewed by Beth Dakin.

WebCore:

Implement support for ARIA "tab", "tabpanel" and "tablist".
As a consequence, we also needed to implement aria-selected
and aria-controls.

Tests: accessibility/aria-controls-with-tabs.html
       accessibility/aria-tab-roles.html

* accessibility/AXObjectCache.cpp:
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityRenderObject.cpp:
* accessibility/AccessibilityRenderObject.h:
* accessibility/mac/AccessibilityObjectWrapper.mm:
* html/HTMLAttributeNames.in:

WebKit:

Add a localizable string for tab panel.

* English.lproj/Localizable.strings:
* StringsNotToBeLocalized.txt:

WebKit/mac:

* WebCoreSupport/WebViewFactory.mm:
(-[WebViewFactory AXARIAContentGroupText:]):

WebKitTools:

* DumpRenderTree/AccessibilityUIElement.cpp:
* DumpRenderTree/AccessibilityUIElement.h:
* DumpRenderTree/gtk/AccessibilityUIElementGtk.cpp:
* DumpRenderTree/mac/AccessibilityUIElementMac.mm:
* DumpRenderTree/win/AccessibilityUIElementWin.cpp:

LayoutTests:

* accessibility/aria-controls-with-tabs-expected.txt: Added.
* accessibility/aria-controls-with-tabs.html: Added.
* accessibility/aria-tab-roles.html: Added.
* platform/gtk/Skipped:
* platform/mac/accessibility/aria-tab-roles-expected.txt: Added.
* platform/win/Skipped:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@50409 268f45cc-cd09-0410-ab3c-d52691b4dbfc

25 files changed:
LayoutTests/ChangeLog
LayoutTests/accessibility/aria-controls-with-tabs-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/aria-controls-with-tabs.html [new file with mode: 0644]
LayoutTests/accessibility/aria-tab-roles.html [new file with mode: 0644]
LayoutTests/platform/gtk/Skipped
LayoutTests/platform/mac/accessibility/aria-tab-roles-expected.txt [new file with mode: 0644]
LayoutTests/platform/win/Skipped
WebCore/ChangeLog
WebCore/accessibility/AXObjectCache.cpp
WebCore/accessibility/AccessibilityObject.h
WebCore/accessibility/AccessibilityRenderObject.cpp
WebCore/accessibility/AccessibilityRenderObject.h
WebCore/accessibility/mac/AccessibilityObjectWrapper.mm
WebCore/html/HTMLAttributeNames.in
WebKit/ChangeLog
WebKit/English.lproj/Localizable.strings
WebKit/StringsNotToBeLocalized.txt
WebKit/mac/ChangeLog
WebKit/mac/WebCoreSupport/WebViewFactory.mm
WebKitTools/ChangeLog
WebKitTools/DumpRenderTree/AccessibilityUIElement.cpp
WebKitTools/DumpRenderTree/AccessibilityUIElement.h
WebKitTools/DumpRenderTree/gtk/AccessibilityUIElementGtk.cpp
WebKitTools/DumpRenderTree/mac/AccessibilityUIElementMac.mm
WebKitTools/DumpRenderTree/win/AccessibilityUIElementWin.cpp

index f80dff8..c9c7b42 100644 (file)
@@ -1,3 +1,17 @@
+2009-11-02  Chris Fleizach  <cfleizach@apple.com>
+
+        Reviewed by Beth Dakin.
+
+        Support ARIA "tab" roles
+        https://bugs.webkit.org/show_bug.cgi?id=30842
+
+        * accessibility/aria-controls-with-tabs-expected.txt: Added.
+        * accessibility/aria-controls-with-tabs.html: Added.
+        * accessibility/aria-tab-roles.html: Added.
+        * platform/gtk/Skipped:
+        * platform/mac/accessibility/aria-tab-roles-expected.txt: Added.
+        * platform/win/Skipped:
+
 2009-11-02  Roland Steiner  <rolandsteiner@chromium.org>
 
         Reviewed by Dave Hyatt.
diff --git a/LayoutTests/accessibility/aria-controls-with-tabs-expected.txt b/LayoutTests/accessibility/aria-controls-with-tabs-expected.txt
new file mode 100644 (file)
index 0000000..7e3bcbf
--- /dev/null
@@ -0,0 +1,21 @@
+Crust
+Veges
+Test
+
+Select Crust
+
+Select Crust
+
+This tests that the aria tab item becomes selected if either aria-selected is used, or if aria-controls points to an item that contains KB focus.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS tab2.isSelected is true
+PASS tab1.isSelected is false
+PASS tab2.isSelected is false
+PASS tab1.isSelected is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/aria-controls-with-tabs.html b/LayoutTests/accessibility/aria-controls-with-tabs.html
new file mode 100644 (file)
index 0000000..382e99e
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css">
+<script>
+var successfullyParsed = false;
+</script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+<body id="body">
+
+<ul id="tablist_1" role="tablist">
+<li id="tab_1" role="tab" tabindex="-1" class="">Crust</li>
+<li id="tab_2" role="tab" tabindex="-1" aria-controls="panel_2" class="">Veges</li>
+</ul>
+
+<h3 tabindex=0 id="elementOutsideTabs">Test</h3>
+
+<div id="panel_1" role="tabpanel" >
+<h3 tabindex=0>Select Crust</h3>
+</div>
+
+<div id="panel_2" role="tabpanel" >
+<h2 id="itemInPanel2" tabindex=0>Select Crust</h2>
+</div>
+
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+    description("This tests that the aria tab item becomes selected if either aria-selected is used, or if aria-controls points to an item that contains KB focus.");
+
+    if (window.accessibilityController) {
+
+          var body = accessibilityController.rootElement;
+          var tabList = body.childAtIndex(0).childAtIndex(0);
+          var tab1 = tabList.childAtIndex(0);
+          var tab2 = tabList.childAtIndex(1);
+
+          // we set KB focus to something in panel_2, which means that tab2 should become selected
+          // because it aria-controls panel_2
+          var panel2Item = document.getElementById("itemInPanel2");
+          panel2Item.focus();
+
+          shouldBe("tab2.isSelected", "true");
+
+          // reset KB focus and verify that neither tab is selected
+          document.getElementById("elementOutsideTabs").focus();
+          shouldBe("tab1.isSelected", "false");
+          shouldBe("tab2.isSelected", "false");
+
+          // Now we set aria-selected to be true on tab1 so that it should become selected
+          document.getElementById("tab_1").setAttribute("aria-selected", "true");
+          shouldBe("tab1.isSelected", "true");          
+    }
+
+    successfullyParsed = true;
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/accessibility/aria-tab-roles.html b/LayoutTests/accessibility/aria-tab-roles.html
new file mode 100644 (file)
index 0000000..d2a424b
--- /dev/null
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css">
+<script>
+var successfullyParsed = false;
+</script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+<body id="body">
+
+<ul id="tablist_1" role="tablist">
+<li id="tab_1" role="tab" tabindex="0" class="">Crust</li>
+<li id="tab_2" role="tab" tabindex="0" class="">Veges</li>
+</ul>
+
+<div id="panel_1" role="tabpanel" aria-labelledby="tab_1" >
+<h3>Select Crust</h3>
+</div>
+
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+
+    description("This tests that the aria roles for tab, tabpanel and tablist work as expected correctly.");
+
+    if (window.accessibilityController) {
+
+          var body = document.getElementById("body");
+          body.focus();
+
+          var tabList = accessibilityController.focusedElement.childAtIndex(0);
+          var tab1 = tabList.childAtIndex(0);
+          var tab2 = tabList.childAtIndex(1);
+          var tabPanel = accessibilityController.focusedElement.childAtIndex(1);
+
+          shouldBe("tabList.role", "'AXRole: AXTabGroup'");
+          shouldBe("tab1.role", "'AXRole: AXRadioButton'");
+          shouldBe("tab1.title", "'AXTitle: Crust'");
+          shouldBe("tab1.childrenCount", "0");
+          shouldBe("tab2.role", "'AXRole: AXRadioButton'");
+          shouldBe("tab2.title", "'AXTitle: Veges'");
+          shouldBe("tabPanel.role", "'AXRole: AXGroup'");
+          shouldBe("tabPanel.subrole", "'AXSubrole: AXTabPanel'");
+    }
+
+    successfullyParsed = true;
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index bc0d2ad..c70dcf3 100644 (file)
@@ -49,6 +49,7 @@ accessibility/aria-label.html
 accessibility/aria-labelledby-stay-within.html
 accessibility/aria-link-supports-press.html
 accessibility/aria-readonly.html
+accessibility/aria-tab-roles.html
 accessibility/button-press-action.html
 accessibility/canvas.html
 accessibility/editable-webarea-context-menu-point.html
diff --git a/LayoutTests/platform/mac/accessibility/aria-tab-roles-expected.txt b/LayoutTests/platform/mac/accessibility/aria-tab-roles-expected.txt
new file mode 100644 (file)
index 0000000..fd9cae3
--- /dev/null
@@ -0,0 +1,21 @@
+Crust
+Veges
+Select Crust
+
+This tests that the aria roles for tab, tabpanel and tablist work as expected correctly.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS tabList.role is 'AXRole: AXTabGroup'
+PASS tab1.role is 'AXRole: AXRadioButton'
+PASS tab1.title is 'AXTitle: Crust'
+PASS tab1.childrenCount is 0
+PASS tab2.role is 'AXRole: AXRadioButton'
+PASS tab2.title is 'AXTitle: Veges'
+PASS tabPanel.role is 'AXRole: AXGroup'
+PASS tabPanel.subrole is 'AXSubrole: AXTabPanel'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index d8a7e32..2cb1413 100644 (file)
@@ -363,6 +363,7 @@ accessibility/aria-presentational-role.html
 accessibility/aria-readonly.html
 accessibility/aria-roles.html
 accessibility/aria-tables.html
+accessibility/aria-tab-roles.html
 accessibility/button-press-action.html
 accessibility/canvas.html
 accessibility/editable-webarea-context-menu-point.html
index 0da74fc..4dee88d 100644 (file)
@@ -1,3 +1,24 @@
+2009-11-02  Chris Fleizach  <cfleizach@apple.com>
+
+        Reviewed by Beth Dakin.
+
+        Support ARIA "tab" roles
+        https://bugs.webkit.org/show_bug.cgi?id=30842
+
+        Implement support for ARIA "tab", "tabpanel" and "tablist".
+        As a consequence, we also needed to implement aria-selected
+        and aria-controls.
+
+        Tests: accessibility/aria-controls-with-tabs.html
+               accessibility/aria-tab-roles.html
+
+        * accessibility/AXObjectCache.cpp:
+        * accessibility/AccessibilityObject.h:
+        * accessibility/AccessibilityRenderObject.cpp:
+        * accessibility/AccessibilityRenderObject.h:
+        * accessibility/mac/AccessibilityObjectWrapper.mm:
+        * html/HTMLAttributeNames.in:
+
 2009-10-27  Stephen White  <senorblanco@chromium.org>
 
         Reviewed by Dmitry Titov.
index 55199a3..5fff690 100644 (file)
@@ -141,7 +141,10 @@ AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
         RefPtr<AccessibilityObject> newObj = 0;
         if (renderer->isListBox())
             newObj = AccessibilityListBox::create(renderer);
-        else if (node && (nodeIsAriaType(node, "list") || node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))
+
+        // If the node is aria role="list" or the aria role is empty and its a ul/ol/dl type (it shouldn't be a list if aria says otherwise). 
+        else if (node && (nodeIsAriaType(node, "list") 
+                          || (nodeIsAriaType(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
             newObj = AccessibilityList::create(renderer);
         
         // aria tables
index cf5d676..d2cf3aa 100644 (file)
@@ -161,6 +161,9 @@ enum AccessibilityRole {
     AnnotationRole,
     SliderThumbRole,
     IgnoredRole,
+    TabRole,
+    TabListRole,
+    TabPanelRole,
     
     // ARIA Grouping roles
     LandmarkApplicationRole,
@@ -269,6 +272,8 @@ public:
     virtual bool isTableCell() const { return false; };
     virtual bool isFieldset() const { return false; };
     virtual bool isGroup() const { return false; };
+    bool isTabList() const { return roleValue() == TabListRole; }
+    bool isTabItem() const { return roleValue() == TabRole; }
     bool isRadioGroup() const { return roleValue() == RadioGroupRole; }
     
     virtual bool isChecked() const { return false; };
@@ -304,6 +309,7 @@ public:
     virtual float maxValueForRange() const { return 0.0f; }
     virtual float minValueForRange() const { return 0.0f; }
     virtual AccessibilityObject* selectedRadioButton() { return 0; }
+    virtual AccessibilityObject* selectedTabItem() { return 0; }    
     virtual int layoutCount() const { return 0; }
     static bool isARIAControl(AccessibilityRole);
     static bool isARIAInput(AccessibilityRole);
@@ -332,7 +338,6 @@ public:
 
     void setRoleValue(AccessibilityRole role) { m_role = role; }
     virtual AccessibilityRole roleValue() const { return m_role; }
-    virtual String ariaAccessibilityName(const String&) const { return String(); }
     virtual String ariaLabeledByAttribute() const { return String(); }
     virtual String ariaDescribedByAttribute() const { return String(); }
     virtual String accessibilityDescription() const { return String(); }
@@ -393,6 +398,7 @@ public:
     virtual bool hasChildren() const { return m_haveChildren; }
     virtual void selectedChildren(AccessibilityChildrenVector&) { }
     virtual void visibleChildren(AccessibilityChildrenVector&) { }
+    virtual void tabChildren(AccessibilityChildrenVector&) { }
     virtual bool shouldFocusActiveDescendant() const { return false; }
     virtual AccessibilityObject* activeDescendant() const { return 0; }    
     virtual void handleActiveDescendantChanged() { }
index c293dcd..2d22268 100644 (file)
@@ -505,6 +505,24 @@ AccessibilityObject* AccessibilityRenderObject::selectedRadioButton()
     }
     return 0;
 }
+
+AccessibilityObject* AccessibilityRenderObject::selectedTabItem()
+{
+    if (!isTabList())
+        return 0;
+    
+    // Find the child tab item that is selected (ie. the intValue == 1).
+    AccessibilityObject::AccessibilityChildrenVector tabs;
+    tabChildren(tabs);
+    
+    int count = tabs.size();
+    for (int i = 0; i < count; ++i) {
+        AccessibilityObject* object = m_children[i].get();
+        if (object->isTabItem() && object->intValue() == 1)
+            return object;
+    }
+    return 0;
+}
     
 const AtomicString& AccessibilityRenderObject::getAttribute(const QualifiedName& attribute) const
 {
@@ -864,56 +882,67 @@ static String accessibleNameForNode(Node* node)
     return String();
 }
 
-String AccessibilityRenderObject::ariaAccessibilityName(const String& s) const
+String AccessibilityRenderObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
+{
+    Vector<UChar> ariaLabel;
+    unsigned size = elements.size();
+    for (unsigned i = 0; i < size; ++i) {
+        Element* idElement = elements[i];
+        
+        String nameFragment = accessibleNameForNode(idElement);
+        ariaLabel.append(nameFragment.characters(), nameFragment.length());
+        for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement)) {
+            nameFragment = accessibleNameForNode(n);
+            ariaLabel.append(nameFragment.characters(), nameFragment.length());
+        }
+            
+        if (i != size - 1)
+            ariaLabel.append(' ');
+    }
+    return String::adopt(ariaLabel);
+}
+
+    
+void AccessibilityRenderObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
 {
+    Node* node = m_renderer->node();
+    if (!node || !node->isElementNode())
+        return;
+
     Document* document = m_renderer->document();
     if (!document)
-        return String();
-
-    String idList = s;
+        return;
+    
+    String idList = getAttribute(attribute).string();
+    if (idList.isEmpty())
+        return;
+    
     idList.replace('\n', ' ');
     Vector<String> idVector;
     idList.split(' ', idVector);
-
-    Vector<UChar> ariaLabel;
+    
     unsigned size = idVector.size();
     for (unsigned i = 0; i < size; ++i) {
         String idName = idVector[i];
         Element* idElement = document->getElementById(idName);
-        if (idElement) {
-            String nameFragment = accessibleNameForNode(idElement);
-            ariaLabel.append(nameFragment.characters(), nameFragment.length());
-            for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement)) {
-                nameFragment = accessibleNameForNode(n);
-                ariaLabel.append(nameFragment.characters(), nameFragment.length());
-            }
-            
-            if (i != size - 1)
-                ariaLabel.append(' ');
-        }
+        if (idElement)
+            elements.append(idElement);
     }
-    return String::adopt(ariaLabel);
 }
-
+    
+void AccessibilityRenderObject::ariaLabeledByElements(Vector<Element*>& elements) const
+{
+    elementsFromAttribute(elements, aria_labeledbyAttr);
+    if (!elements.size())
+        elementsFromAttribute(elements, aria_labelledbyAttr);
+}
+   
 String AccessibilityRenderObject::ariaLabeledByAttribute() const
 {
-    Node* node = m_renderer->node();
-    if (!node)
-        return String();
-
-    if (!node->isElementNode())
-        return String();
-
-    // The ARIA spec uses the British spelling: "labelled." It seems prudent to support the American
-    // spelling ("labeled") as well.
-    String idList = getAttribute(aria_labeledbyAttr).string();
-    if (idList.isEmpty()) {
-        idList = getAttribute(aria_labelledbyAttr).string();
-        if (idList.isEmpty())
-            return String();
-    }
-
-    return ariaAccessibilityName(idList);
+    Vector<Element*> elements;
+    ariaLabeledByElements(elements);
+    
+    return accessibilityDescriptionForElements(elements);
 }
 
 static HTMLLabelElement* labelForElement(Element* element)
@@ -990,6 +1019,7 @@ String AccessibilityRenderObject::title() const
         || ariaRole == MenuItemRole
         || ariaRole == MenuButtonRole
         || ariaRole == RadioButtonRole
+        || ariaRole == TabRole
         || isHeading())
         return textUnderElement();
     
@@ -1001,11 +1031,10 @@ String AccessibilityRenderObject::title() const
 
 String AccessibilityRenderObject::ariaDescribedByAttribute() const
 {
-    String idList = getAttribute(aria_describedbyAttr).string();
-    if (idList.isEmpty())
-        return String();
+    Vector<Element*> elements;
+    elementsFromAttribute(elements, aria_describedbyAttr);
     
-    return ariaAccessibilityName(idList);
+    return accessibilityDescriptionForElements(elements);
 }
 
 String AccessibilityRenderObject::accessibilityDescription() const
@@ -1562,9 +1591,55 @@ bool AccessibilityRenderObject::isSelected() const
     if (!node)
         return false;
     
+    if (equalIgnoringCase(getAttribute(aria_selectedAttr).string(), "true"))
+        return true;    
+    
+    if (isTabItem() && isTabItemSelected())
+        return true;
+
     return false;
 }
 
+bool AccessibilityRenderObject::isTabItemSelected() const
+{
+    if (!isTabItem() || !m_renderer)
+        return false;
+    
+    Node* node = m_renderer->node();
+    if (!node || !node->isElementNode())
+        return false;
+    
+    // The ARIA spec says a tab item can also be selected if it is aria-labeled by a tabpanel
+    // that has keyboard focus inside of it, or if a tabpanel in its aria-controls list has KB
+    // focus inside of it.
+    AccessibilityObject* focusedElement = focusedUIElement();
+    if (!focusedElement)
+        return false;
+    
+    Vector<Element*> elements;
+    elementsFromAttribute(elements, aria_controlsAttr);
+    
+    unsigned count = elements.size();
+    for (unsigned k = 0; k < count; ++k) {
+        Element* element = elements[k];
+        AccessibilityObject* tabPanel = axObjectCache()->getOrCreate(element->renderer());
+
+        // A tab item should only control tab panels.
+        if (!tabPanel || tabPanel->roleValue() != TabPanelRole)
+            continue;
+        
+        AccessibilityObject* checkFocusElement = focusedElement;
+        // Check if the focused element is a descendant of the element controlled by the tab item.
+        while (checkFocusElement) {
+            if (tabPanel == checkFocusElement)
+                return true;
+            checkFocusElement = checkFocusElement->parentObject();
+        }
+    }
+    
+    return false;
+}
+    
 bool AccessibilityRenderObject::isFocused() const
 {
     if (!m_renderer)
@@ -2302,6 +2377,9 @@ static const ARIARoleMap& createARIARoleMap()
         { "slider", SliderRole },
         { "spinbutton", ProgressIndicatorRole },
         { "status", ApplicationStatusRole },
+        { "tab", TabRole },
+        { "tablist", TabListRole },
+        { "tabpanel", TabPanelRole },
         { "textbox", TextAreaRole },
         { "timer", ApplicationTimerRole },
         { "toolbar", ToolbarRole },
@@ -2536,6 +2614,7 @@ bool AccessibilityRenderObject::canHaveChildren() const
         case PopUpButtonRole:
         case CheckBoxRole:
         case RadioButtonRole:
+        case TabRole:
         case StaticTextRole:
         case ListBoxOptionRole:
             return false;
@@ -2625,7 +2704,7 @@ void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildre
         if (childRenderer && ariaRole == ListBoxOptionRole) {
             Element* childElement = static_cast<Element*>(childRenderer->node());
             if (childElement && childElement->isElementNode()) { // do this check to ensure safety of static_cast above
-                String selectedAttrString = childElement->getAttribute("aria-selected").string();
+                String selectedAttrString = childElement->getAttribute(aria_selectedAttr).string();
                 if (equalIgnoringCase(selectedAttrString, "true")) {
                     result.append(child);
                     if (isMultiselectable)
@@ -2673,6 +2752,17 @@ void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& res
     return ariaListboxVisibleChildren(result);
 }
  
+void AccessibilityRenderObject::tabChildren(AccessibilityChildrenVector& result)
+{
+    ASSERT(roleValue() == TabListRole);
+    
+    unsigned length = m_children.size();
+    for (unsigned i = 0; i < length; ++i) {
+        if (m_children[i]->isTabItem())
+            result.append(m_children[i]);
+    }
+}
+    
 const String& AccessibilityRenderObject::actionVerb() const
 {
     // FIXME: Need to add verbs for select elements.
index 14b7e34..f265fbe 100644 (file)
@@ -120,6 +120,7 @@ public:
     virtual float maxValueForRange() const;
     virtual float minValueForRange() const;
     virtual AccessibilityObject* selectedRadioButton();
+    virtual AccessibilityObject* selectedTabItem();
     virtual int layoutCount() const;
     
     virtual AccessibilityObject* doAccessibilityHitTest(const IntPoint&) const;
@@ -168,7 +169,6 @@ public:
     virtual PlainTextRange selectedTextRange() const;
     virtual VisibleSelection selection() const;
     virtual String stringValue() const;
-    virtual String ariaAccessibilityName(const String&) const;
     virtual String ariaLabeledByAttribute() const;
     virtual String title() const;
     virtual String ariaDescribedByAttribute() const;
@@ -202,6 +202,7 @@ public:
     virtual bool canHaveChildren() const;
     virtual void selectedChildren(AccessibilityChildrenVector&);
     virtual void visibleChildren(AccessibilityChildrenVector&);
+    virtual void tabChildren(AccessibilityChildrenVector&);
     virtual bool shouldFocusActiveDescendant() const;
     virtual AccessibilityObject* activeDescendant() const;
     virtual void handleActiveDescendantChanged();
@@ -237,6 +238,7 @@ protected:
     mutable bool m_childrenDirty;
     
     void setRenderObject(RenderObject* renderer) { m_renderer = renderer; }
+    void ariaLabeledByElements(Vector<Element*>& elements) const;
     
     virtual bool isDetached() const { return !m_renderer; }
 
@@ -251,12 +253,16 @@ private:
     AccessibilityRole determineAccessibilityRole();
     AccessibilityRole determineAriaRoleAttribute() const;
 
+    bool isTabItemSelected() const;
     IntRect checkboxOrRadioRect() const;
     void addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const;
     AccessibilityObject* internalLinkElement() const;
     AccessibilityObject* accessibilityImageMapHitTest(HTMLAreaElement*, const IntPoint&) const;
     AccessibilityObject* accessibilityParentForImageMap(HTMLMapElement* map) const;
 
+    String accessibilityDescriptionForElements(Vector<Element*> &elements) const;
+    void elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& name) const;
+    
     void markChildrenDirty() const { m_childrenDirty = true; }
 };
     
index 58e5018..009986b 100644 (file)
@@ -600,6 +600,7 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi
     static NSArray* groupAttrs = nil;
     static NSArray* inputImageAttrs = nil;
     static NSArray* passwordFieldAttrs = nil;
+    static NSArray *tabListAttrs = nil;
     NSMutableArray* tempArray;
     if (attributes == nil) {
         attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute,
@@ -794,6 +795,13 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi
         passwordFieldAttrs = [[NSArray alloc] initWithArray:tempArray];
         [tempArray release];
     }
+    if (tabListAttrs == nil) {
+        tempArray = [[NSMutableArray alloc] initWithArray:attributes];
+        [tempArray addObject:NSAccessibilityTabsAttribute];
+        [tempArray addObject:NSAccessibilityContentsAttribute];
+        tabListAttrs = [[NSArray alloc] initWithArray:tempArray];
+        [tempArray release];        
+    }
     
     if (m_object->isPasswordField())
         return passwordFieldAttrs;
@@ -830,6 +838,8 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi
     
     if (m_object->isGroup())
         return groupAttrs;
+    if (m_object->isTabList())
+        return tabListAttrs;
     
     if (m_object->isMenu())
         return menuAttrs;
@@ -1001,8 +1011,9 @@ static const AccessibilityRoleMap& createAccessibilityRoleMap()
         { DocumentNoteRole, NSAccessibilityGroupRole },
         { DocumentRegionRole, NSAccessibilityGroupRole },
         { UserInterfaceTooltipRole, NSAccessibilityGroupRole },
-        
-
+        { TabRole, NSAccessibilityRadioButtonRole },
+        { TabListRole, NSAccessibilityTabGroupRole },
+        { TabPanelRole, NSAccessibilityGroupRole },
     };
     AccessibilityRoleMap& roleMap = *new AccessibilityRoleMap;
     
@@ -1083,6 +1094,8 @@ static NSString* roleValueToNSString(AccessibilityRole value)
             return @"AXDocumentRegion";
         case UserInterfaceTooltipRole:
             return @"AXUserInterfaceTooltip";
+        case TabPanelRole:
+            return @"AXTabPanel";
         default:
             return nil;
     }
@@ -1140,6 +1153,8 @@ static NSString* roleValueToNSString(AccessibilityRole value)
                 return AXARIAContentGroupText(@"ARIADocumentRegion");
             case UserInterfaceTooltipRole:
                 return AXARIAContentGroupText(@"ARIAUserInterfaceTooltip");
+            case TabPanelRole:
+                return AXARIAContentGroupText(@"ARIATabPanel");
         }
     }        
     
@@ -1158,6 +1173,10 @@ static NSString* roleValueToNSString(AccessibilityRole value)
     if ([axRole isEqualToString:@"AXHeading"])
         return AXHeadingText();
 
+    // AppKit also returns AXTab for the role description for a tab item.
+    if (m_object->isTabItem())
+        return NSAccessibilityRoleDescription(@"AXTab", nil);
+    
     // We should try the system default role description for all other roles.
     // If we get the same string back, then as a last resort, return unknown.
     NSString* defaultRoleDescription = NSAccessibilityRoleDescription(axRole, [self subrole]);
@@ -1318,6 +1337,16 @@ static NSString* roleValueToNSString(AccessibilityRole value)
             return radioButton->wrapper();
         }
         
+        if (m_object->isTabList()) {
+            AccessibilityObject* tabItem = m_object->selectedTabItem();
+            if (!tabItem)
+                return nil;
+            return tabItem->wrapper();
+        }
+        
+        if (m_object->isTabItem())
+            return [NSNumber numberWithInt:m_object->isSelected()];
+        
         return m_object->stringValue();
     }
 
@@ -1359,6 +1388,31 @@ static NSString* roleValueToNSString(AccessibilityRole value)
         return accessKey;
     }
     
+    if ([attributeName isEqualToString:NSAccessibilityTabsAttribute]) {
+        if (m_object->isTabList()) {
+            AccessibilityObject::AccessibilityChildrenVector tabsChildren;
+            m_object->tabChildren(tabsChildren);
+            return convertToNSArray(tabsChildren);
+        }
+    }
+    
+    if ([attributeName isEqualToString:NSAccessibilityContentsAttribute]) {
+        // The contents of a tab list are all the children except the tabs.
+        if (m_object->isTabList()) {
+            AccessibilityObject::AccessibilityChildrenVector children = m_object->children();
+            AccessibilityObject::AccessibilityChildrenVector tabsChildren;
+            m_object->tabChildren(tabsChildren);
+
+            AccessibilityObject::AccessibilityChildrenVector contents;
+            unsigned childrenSize = children.size();
+            for (unsigned k = 0; k < childrenSize; ++k) {
+                if (tabsChildren.find(children[k]) == WTF::notFound)
+                    contents.append(children[k]);
+            }
+            return convertToNSArray(contents);
+        }
+    }    
+    
     if (m_object->isDataTable()) {
         // TODO: distinguish between visible and non-visible rows
         if ([attributeName isEqualToString:NSAccessibilityRowsAttribute] || 
index 340779e..671125e 100644 (file)
@@ -14,6 +14,7 @@ alt
 archive
 aria-activedescendant
 aria-checked
+aria-controls
 aria-describedby
 aria-disabled
 aria-hidden
@@ -24,6 +25,7 @@ aria-level
 aria-pressed
 aria-readonly
 aria-required
+aria-selected
 aria-valuemax
 aria-valuemin
 aria-valuenow
index 5006da7..e39f2dd 100644 (file)
@@ -1,3 +1,15 @@
+2009-11-02  Chris Fleizach  <cfleizach@apple.com>
+
+        Reviewed by Beth Dakin.
+
+        Support ARIA "tab" roles
+        https://bugs.webkit.org/show_bug.cgi?id=30842
+
+        Add a localizable string for tab panel.
+
+        * English.lproj/Localizable.strings:
+        * StringsNotToBeLocalized.txt:
+
 2009-10-27  Dan Bernstein  <mitz@apple.com>
 
         Reviewed by Darin Adler.
index e4a172b..3368a33 100644 (file)
Binary files a/WebKit/English.lproj/Localizable.strings and b/WebKit/English.lproj/Localizable.strings differ
index 3f1964d..7951dd8 100644 (file)
 "ARIALandmarkMain"
 "ARIALandmarkNavigation"
 "ARIALandmarkSearch"
+"ARIATabPanel"
 "ARIAUserInterfaceTooltip"
 "AXEnhancedUserInterface"
 "AccessibleBase"
index dc26a96..a8c4d9a 100644 (file)
@@ -1,3 +1,13 @@
+2009-11-02  Chris Fleizach  <cfleizach@apple.com>
+
+        Reviewed by Beth Dakin.
+
+        Support ARIA "tab" roles
+        https://bugs.webkit.org/show_bug.cgi?id=30842
+
+        * WebCoreSupport/WebViewFactory.mm:
+        (-[WebViewFactory AXARIAContentGroupText:]):
+
 2009-11-01  Dan Bernstein  <mitz@apple.com>
 
         Reviewed by Mark Rowe.
index 79b2959..263ea03 100644 (file)
         return UI_STRING("search", "An ARIA accessibility group that contains a search feature of a website.");    
     if ([ariaType isEqualToString:@"ARIAUserInterfaceTooltip"])
         return UI_STRING("tooltip", "An ARIA accessibility group that acts as a tooltip.");    
+    if ([ariaType isEqualToString:@"ARIATabPanel"])
+        return UI_STRING("tab panel", "An ARIA accessibility group that contenst the content of a tab.");
     return nil;
 }
 
index 665ca49..b8feca3 100644 (file)
@@ -1,3 +1,16 @@
+2009-11-02  Chris Fleizach  <cfleizach@apple.com>
+
+        Reviewed by Beth Dakin.
+
+        Support ARIA "tab" roles
+        https://bugs.webkit.org/show_bug.cgi?id=30842
+
+        * DumpRenderTree/AccessibilityUIElement.cpp:
+        * DumpRenderTree/AccessibilityUIElement.h:
+        * DumpRenderTree/gtk/AccessibilityUIElementGtk.cpp:
+        * DumpRenderTree/mac/AccessibilityUIElementMac.mm:
+        * DumpRenderTree/win/AccessibilityUIElementWin.cpp:
+
 2009-11-01  Eric Seidel  <eric@webkit.org>
 
         Reviewed by David Levin.
index 1ed8217..aca4a16 100644 (file)
@@ -281,6 +281,12 @@ static JSValueRef getDescriptionCallback(JSContextRef context, JSObjectRef thisO
     return JSValueMakeString(context, description.get());
 }
 
+static JSValueRef getStringValueCallback(JSContextRef context, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception)
+{
+    JSRetainPtr<JSStringRef> stringValue(Adopt, toAXElement(thisObject)->stringValue());
+    return JSValueMakeString(context, stringValue.get());
+}
+
 static JSValueRef getLanguageCallback(JSContextRef context, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception)
 {
     JSRetainPtr<JSStringRef> language(Adopt, toAXElement(thisObject)->language());
@@ -358,6 +364,11 @@ static JSValueRef getIsRequiredCallback(JSContextRef context, JSObjectRef thisOb
     return JSValueMakeBoolean(context, toAXElement(thisObject)->isRequired());
 }
 
+static JSValueRef getIsSelectedCallback(JSContextRef context, JSObjectRef thisObject, JSStringRef, JSValueRef*)
+{
+    return JSValueMakeBoolean(context, toAXElement(thisObject)->isSelected());
+}
+
 static JSValueRef getValueDescriptionCallback(JSContextRef context, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception)
 {
     JSRetainPtr<JSStringRef> valueDescription(Adopt, toAXElement(thisObject)->valueDescription());
@@ -393,6 +404,7 @@ JSClassRef AccessibilityUIElement::getJSClass()
         { "title", getTitleCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "description", getDescriptionCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "language", getLanguageCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "stringValue", getStringValueCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "x", getXCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "y", getYCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "width", getWidthCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
@@ -407,6 +419,7 @@ JSClassRef AccessibilityUIElement::getJSClass()
         { "selectedTextRange", getSelectedTextRangeCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "isEnabled", getIsEnabledCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "isRequired", getIsRequiredCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "isSelected", getIsSelectedCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "valueDescription", getValueDescriptionCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { 0, 0, 0, 0 }
     };
index ea45a27..9a72d41 100644 (file)
@@ -90,6 +90,7 @@ public:
     JSStringRef title();
     JSStringRef description();
     JSStringRef language();
+    JSStringRef stringValue();
     JSStringRef accessibilityValue() const;
     double x();
     double y();
@@ -103,6 +104,7 @@ public:
     JSStringRef selectedTextRange();
     bool isEnabled();
     bool isRequired() const;
+    bool isSelected() const;
     double clickPointX();
     double clickPointY();
 
index e5f26d2..925c658 100644 (file)
@@ -181,6 +181,12 @@ JSStringRef AccessibilityUIElement::description()
     return JSStringCreateWithUTF8CString(description);
 }
 
+JSStringRef AccessibilityUIElement::stringValue()
+{
+    // FIXME: implement
+    return JSStringCreateWithCharacters(0, 0);
+}
+
 JSStringRef AccessibilityUIElement::language()
 {
     // FIXME: implement
@@ -316,6 +322,12 @@ bool AccessibilityUIElement::isRequired() const
     return false;
 }
 
+bool AccessibilityUIElement::isSelected() const
+{
+    // FIXME: implement
+    return false;
+}
+
 JSStringRef AccessibilityUIElement::attributesOfColumnHeaders()
 {
     // FIXME: implement
index ef0f9da..6a41656 100644 (file)
@@ -329,6 +329,12 @@ JSStringRef AccessibilityUIElement::description()
     return concatenateAttributeAndValue(@"AXDescription", description);
 }
 
+JSStringRef AccessibilityUIElement::stringValue()
+{
+    id description = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityValueAttribute], m_element);
+    return concatenateAttributeAndValue(@"AXValue", description);
+}
+
 JSStringRef AccessibilityUIElement::language()
 {
     id description = descriptionOfValue([m_element accessibilityAttributeValue:@"AXLanguage"], m_element);
@@ -433,6 +439,14 @@ bool AccessibilityUIElement::isRequired() const
     return false;
 }
 
+bool AccessibilityUIElement::isSelected() const
+{
+    id value = [m_element accessibilityAttributeValue:NSAccessibilitySelectedAttribute];
+    if ([value isKindOfClass:[NSNumber class]])
+        return [value boolValue];
+    return false;
+}
+
 // parameterized attributes
 int AccessibilityUIElement::lineForIndex(int index)
 {
index 2a8eaf6..1714da1 100644 (file)
@@ -196,6 +196,11 @@ JSStringRef AccessibilityUIElement::description()
     return JSStringCreateWithCharacters(description.data(), description.length());
 }
 
+JSStringRef AccessibilityUIElement::stringValue()
+{
+    return JSStringCreateWithCharacters(0, 0);
+}
+
 JSStringRef AccessibilityUIElement::language()
 {
     return JSStringCreateWithCharacters(0, 0);
@@ -284,6 +289,11 @@ bool AccessibilityUIElement::isRequired() const
     return false;
 }
 
+bool AccessibilityUIElement::isSelected() const
+{
+    return false;
+}
+
 int AccessibilityUIElement::insertionPointLineNumber()
 {
     return 0;