2008-05-06 Beth Dakin <bdakin@apple.com>
authorbdakin@apple.com <bdakin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 7 May 2008 01:47:47 +0000 (01:47 +0000)
committerbdakin@apple.com <bdakin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 7 May 2008 01:47:47 +0000 (01:47 +0000)
        Reviewed by Darin.

        Fix for <rdar://problem/5907916> Implement 'aria-labeledby' and
        'aria-describedby' attributes.

        * html/HTMLAttributeNames.in: Added new attributes. Added both the
        British spelling (since that is what is specified in the spec), and
        the American spelling (since the bug filer and I are two Americans
        who keep spelling it the American way by accident).
        * page/AccessibilityObject.cpp: Added empty wrappers. These
        functions can't do anything meaningful without a renderer.
        (WebCore::AccessibilityObject::ariaAccessiblityName):
        (WebCore::AccessibilityObject::ariaLabeledByAttribute):
        (WebCore::AccessibilityObject::ariaDescribedByAttribute):
        * page/AccessibilityObject.h:

        Here is where the real work is done.
        * page/AccessibilityRenderObject.h:
        * page/AccessibilityRenderObject.cpp:
        (WebCore::accessibleNameForNode): Takes a node and finds its
        contribution to the accessible name, as defined by the Mozilla ARIA
        Implementer's Guide.
        (WebCore::AccessibilityRenderObject::ariaAccessiblityName): Takes a
        string of space-separated IDs, fetches the corresponding element
        for each ID, and concatenates an accessible name based on the
        elements.
        (WebCore::AccessibilityRenderObject::ariaLabeledByAttribute):
        Retrieve the labeledby attribute and send its contents to
        ariaAccessibilityName().
        (WebCore::AccessibilityRenderObject::title): Return the ARIA
        labeledby value if one exists.
        (WebCore::AccessibilityRenderObject::ariaDescribedByAttribute):
        Retrieve the describedby attribute and send its contents to
        ariaAccessibilityName().
        (WebCore::AccessibilityRenderObject::accessibilityDescription):
        Return the ARIA describedby attribute if one exists.

        These are two bugs I spotted.
        (WebCore::AccessibilityRenderObject::accessibilityIsIgnored): Don't
        ignore anything with an ARIA role.
        (WebCore::AccessibilityRenderObject::roleValue): Button tags maps
        to ButtonRole.

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

WebCore/ChangeLog
WebCore/html/HTMLAttributeNames.in
WebCore/page/AccessibilityObject.cpp
WebCore/page/AccessibilityObject.h
WebCore/page/AccessibilityRenderObject.cpp
WebCore/page/AccessibilityRenderObject.h

index 18dd714c2c2dd45e49323b66380685ac68b8287e..d779d2b1eea5f5124987fda5d6c65f3762166bfb 100644 (file)
@@ -1,3 +1,48 @@
+2008-05-06  Beth Dakin  <bdakin@apple.com>
+
+        Reviewed by Darin.
+
+        Fix for <rdar://problem/5907916> Implement 'aria-labeledby' and 
+        'aria-describedby' attributes.
+
+        * html/HTMLAttributeNames.in: Added new attributes. Added both the 
+        British spelling (since that is what is specified in the spec), and 
+        the American spelling (since the bug filer and I are two Americans 
+        who keep spelling it the American way by accident). 
+        * page/AccessibilityObject.cpp: Added empty wrappers. These 
+        functions can't do anything meaningful without a renderer.
+        (WebCore::AccessibilityObject::ariaAccessiblityName):
+        (WebCore::AccessibilityObject::ariaLabeledByAttribute):
+        (WebCore::AccessibilityObject::ariaDescribedByAttribute):
+        * page/AccessibilityObject.h:
+
+        Here is where the real work is done.
+        * page/AccessibilityRenderObject.h:
+        * page/AccessibilityRenderObject.cpp:
+        (WebCore::accessibleNameForNode): Takes a node and finds its 
+        contribution to the accessible name, as defined by the Mozilla ARIA 
+        Implementer's Guide. 
+        (WebCore::AccessibilityRenderObject::ariaAccessiblityName): Takes a 
+        string of space-separated IDs, fetches the corresponding element 
+        for each ID, and concatenates an accessible name based on the 
+        elements.
+        (WebCore::AccessibilityRenderObject::ariaLabeledByAttribute): 
+        Retrieve the labeledby attribute and send its contents to 
+        ariaAccessibilityName().
+        (WebCore::AccessibilityRenderObject::title): Return the ARIA 
+        labeledby value if one exists.
+        (WebCore::AccessibilityRenderObject::ariaDescribedByAttribute): 
+        Retrieve the describedby attribute and send its contents to 
+        ariaAccessibilityName().
+        (WebCore::AccessibilityRenderObject::accessibilityDescription): 
+        Return the ARIA describedby attribute if one exists. 
+
+        These are two bugs I spotted. 
+        (WebCore::AccessibilityRenderObject::accessibilityIsIgnored): Don't 
+        ignore anything with an ARIA role.
+        (WebCore::AccessibilityRenderObject::roleValue): Button tags maps 
+        to ButtonRole.
+
 2008-05-06  Anders Carlsson  <andersca@apple.com>
 
         Reviewed by Brady.
index 468bee6d0df1962684b18d86e7d478d55e902b3c..ae64c7fd0f45194fb8da10be4644c6c0a896ec87 100644 (file)
@@ -7,6 +7,9 @@ align
 alink
 alt
 archive
+aria-labeledby
+aria-labelledby
+aria-describedby
 autocomplete
 autoplay
 autosave
index 0914f3a811908aad0b57c188adf69cacf7766f2f..9820c89eb5da1d7947de7bd8673c52e4f9939ee5 100644 (file)
@@ -158,11 +158,26 @@ String AccessibilityObject::stringValue() const
     return String();
 }
 
+String AccessibilityObject::ariaAccessiblityName(const String&) const
+{
+    return String();
+}
+
+String AccessibilityObject::ariaLabeledByAttribute() const
+{
+    return String();
+}
+
 String AccessibilityObject::title() const
 {
     return String();
 }
 
+String AccessibilityObject::ariaDescribedByAttribute() const
+{
+    return String();
+}
+
 String AccessibilityObject::accessibilityDescription() const
 {
     return String();
index 4cdfc5dee825bdfee1eb063b8927f4a1fa15a35c..5079c7cd0aa6e2c2b2617e17e13d6cc81fd0328e 100644 (file)
@@ -254,7 +254,10 @@ public:
     unsigned selectionStart() const;
     unsigned selectionEnd() const;
     virtual String stringValue() const;
+    virtual String ariaAccessiblityName(const String&) const;
+    virtual String ariaLabeledByAttribute() const;
     virtual String title() const;
+    virtual String ariaDescribedByAttribute() const;
     virtual String accessibilityDescription() const;
     virtual String helpText() const;
     virtual String textUnderElement() const;
index ba7eb41621c4c0776e02e5f1ef02c404d48c1297..628f58e05d75ad934bf34d232f52e563b2a3e4a0 100644 (file)
@@ -63,6 +63,7 @@
 #include "RenderView.h"
 #include "RenderWidget.h"
 #include "SelectionController.h"
+#include "Text.h"
 #include "TextIterator.h"
 #include "htmlediting.h"
 #include "visible_units.h"
@@ -502,6 +503,73 @@ String AccessibilityRenderObject::stringValue() const
     return String();
 }
 
+// This function implements the ARIA accessible name as described by the Mozilla
+// ARIA Implementer's Guide.
+static String accessibleNameForNode(Node* node)
+{
+    if (node->isTextNode())
+        return static_cast<Text*>(node)->data();
+
+    if (node->hasTagName(inputTag))
+        return static_cast<HTMLInputElement*>(node)->value();
+
+    if (node->isHTMLElement()) {
+        const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr);
+        if (!alt.isEmpty())
+            return alt;
+    }
+
+    return String();
+}
+
+String AccessibilityRenderObject::ariaAccessiblityName(const String& s) const
+{
+    Document* document = m_renderer->document();
+    if (!document)
+        return String();
+
+    String idList = s;
+    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->nextSibling())) {
+                nameFragment = accessibleNameForNode(n);
+                ariaLabel.append(nameFragment.characters(), nameFragment.length());
+            }
+            ariaLabel.append(' ');
+        }
+    }
+    return String::adopt(ariaLabel);
+}
+
+String AccessibilityRenderObject::ariaLabeledByAttribute() const
+{
+    Node* node = m_renderer->node();
+    if (!node || !node->isElementNode())
+        return String();
+
+    // The ARIA spec uses the British spelling: "labelled." It seems prudent to support the American
+    // spelling ("labeled") as well.
+    Element* element = static_cast<Element*>(node);
+    String idList = element->getAttribute(aria_labeledbyAttr).string();
+    if (idList.isEmpty()) {
+        idList = element->getAttribute(aria_labelledbyAttr).string();
+        if (idList.isEmpty())
+            return String();
+    }
+
+    return ariaAccessiblityName(idList);
+}
+
 static HTMLLabelElement* labelForElement(Element* element)
 {
     RefPtr<NodeList> list = element->document()->getElementsByTagName("label");
@@ -521,6 +589,10 @@ String AccessibilityRenderObject::title() const
 {
     if (!m_renderer || m_areaElement || !m_renderer->element())
         return String();
+
+    String ariaLabel = ariaLabeledByAttribute();
+    if (!ariaLabel.isEmpty())
+        return ariaLabel;
     
     if (roleValue() == ButtonRole)
         return textUnderElement();
@@ -544,10 +616,28 @@ String AccessibilityRenderObject::title() const
     return String();
 }
 
+String AccessibilityRenderObject::ariaDescribedByAttribute() const
+{
+    Node* node = m_renderer->node();
+    if (!node || !node->isElementNode())
+        return String();
+
+    Element* element = static_cast<Element*>(node);
+    String idList = element->getAttribute(aria_describedbyAttr).string();
+    if (idList.isEmpty())
+        return String();
+    
+    return ariaAccessiblityName(idList);
+}
+
 String AccessibilityRenderObject::accessibilityDescription() const
 {
     if (!m_renderer || m_areaElement)
         return String();
+
+    String ariaDescription = ariaDescribedByAttribute();
+    if (!ariaDescription.isEmpty())
+        return ariaDescription;
     
     if (isImage()) {
         if (m_renderer->element() && m_renderer->element()->isHTMLElement()) {
@@ -666,6 +756,9 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const
     // ignore invisible element
     if (!m_renderer || m_renderer->style()->visibility() != VISIBLE)
         return true;
+
+    if (ariaRoleAttribute() != UnknownRole)
+        return false;
     
     // ignore popup menu items because AppKit does
     for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) {
@@ -1469,7 +1562,10 @@ AccessibilityRole AccessibilityRenderObject::roleValue() const
         if (input->isTextButton())
             return ButtonRole;
     }
-    
+
+    if (node && node->hasTagName(buttonTag))
+        return ButtonRole;
+
     if (m_renderer->isMenuList())
         return PopUpButtonRole;
     
index c28275123b06278a1e518c367b0a6da75b0cdb78..facc8710828c4f84441abb3a1c6649b66c88e2b1 100644 (file)
@@ -132,7 +132,10 @@ public:
     virtual PlainTextRange selectedTextRange() const;
     virtual Selection selection() const;
     virtual String stringValue() const;
+    virtual String ariaAccessiblityName(const String&) const;
+    virtual String ariaLabeledByAttribute() const;
     virtual String title() const;
+    virtual String ariaDescribedByAttribute() const;
     virtual String accessibilityDescription() const;
     virtual String helpText() const;
     virtual String textUnderElement() const;