diff options
Diffstat (limited to 'WebCore/accessibility')
32 files changed, 2037 insertions, 685 deletions
diff --git a/WebCore/accessibility/AXObjectCache.cpp b/WebCore/accessibility/AXObjectCache.cpp index 55199a3..6ec325a 100644 --- a/WebCore/accessibility/AXObjectCache.cpp +++ b/WebCore/accessibility/AXObjectCache.cpp @@ -30,12 +30,12 @@ #include "AXObjectCache.h" #include "AccessibilityARIAGrid.h" -#include "AccessibilityARIAGridRow.h" #include "AccessibilityARIAGridCell.h" +#include "AccessibilityARIAGridRow.h" +#include "AccessibilityImageMapLink.h" #include "AccessibilityList.h" #include "AccessibilityListBox.h" #include "AccessibilityListBoxOption.h" -#include "AccessibilityImageMapLink.h" #include "AccessibilityMediaControls.h" #include "AccessibilityRenderObject.h" #include "AccessibilitySlider.h" @@ -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, "directory")) + || (nodeIsAriaType(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag))))) newObj = AccessibilityList::create(renderer); // aria tables @@ -191,23 +194,23 @@ AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) // will be filled in... switch (role) { - case ListBoxOptionRole: - obj = AccessibilityListBoxOption::create(); - break; - case ImageMapLinkRole: - obj = AccessibilityImageMapLink::create(); - break; - case ColumnRole: - obj = AccessibilityTableColumn::create(); - break; - case TableHeaderContainerRole: - obj = AccessibilityTableHeaderContainer::create(); - break; - case SliderThumbRole: - obj = AccessibilitySliderThumb::create(); - break; - default: - obj = 0; + case ListBoxOptionRole: + obj = AccessibilityListBoxOption::create(); + break; + case ImageMapLinkRole: + obj = AccessibilityImageMapLink::create(); + break; + case ColumnRole: + obj = AccessibilityTableColumn::create(); + break; + case TableHeaderContainerRole: + obj = AccessibilityTableHeaderContainer::create(); + break; + case SliderThumbRole: + obj = AccessibilitySliderThumb::create(); + break; + default: + obj = 0; } if (obj) @@ -235,9 +238,8 @@ void AXObjectCache::remove(AXID axID) removeAXID(obj); // finally remove the object - if (!m_objects.take(axID)) { + if (!m_objects.take(axID)) return; - } ASSERT(m_objects.size() >= m_idsInUse.size()); } @@ -261,7 +263,7 @@ AXID AXObjectCache::platformGenerateAXID() const AXID objID = lastUsedID; do { ++objID; - } while (objID == 0 || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); + } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); lastUsedID = objID; @@ -292,7 +294,7 @@ void AXObjectCache::removeAXID(AccessibilityObject* obj) return; AXID objID = obj->axObjectID(); - if (objID == 0) + if (!objID) return; ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); ASSERT(m_idsInUse.contains(objID)); diff --git a/WebCore/accessibility/AXObjectCache.h b/WebCore/accessibility/AXObjectCache.h index 5a75f84..adfddcd 100644 --- a/WebCore/accessibility/AXObjectCache.h +++ b/WebCore/accessibility/AXObjectCache.h @@ -42,104 +42,105 @@ class WebCoreTextMarker; namespace WebCore { - class Node; - class Page; - class RenderObject; - class String; - class VisiblePosition; - - struct TextMarkerData { - AXID axID; - Node* node; - int offset; - EAffinity affinity; - }; - - class AXObjectCache { - public: - AXObjectCache(); - ~AXObjectCache(); - - static AccessibilityObject* focusedUIElementForPage(const Page*); - - // to be used with render objects - AccessibilityObject* getOrCreate(RenderObject*); - - // used for objects without backing elements - AccessibilityObject* getOrCreate(AccessibilityRole); - - // will only return the AccessibilityObject if it already exists - AccessibilityObject* get(RenderObject*); - - void remove(RenderObject*); - void remove(AXID); - - void detachWrapper(AccessibilityObject*); - void attachWrapper(AccessibilityObject*); - void childrenChanged(RenderObject*); - void selectedChildrenChanged(RenderObject*); - void handleActiveDescendantChanged(RenderObject*); - void handleAriaRoleChanged(RenderObject*); - void handleFocusedUIElementChanged(RenderObject* oldFocusedRenderer, RenderObject* newFocusedRenderer); - void handleScrolledToAnchor(const Node* anchorNode); - - static void enableAccessibility() { gAccessibilityEnabled = true; } - static void enableEnhancedUserInterfaceAccessibility() { gAccessibilityEnhancedUserInterfaceEnabled = true; } - - static bool accessibilityEnabled() { return gAccessibilityEnabled; } - static bool accessibilityEnhancedUserInterfaceEnabled() { return gAccessibilityEnhancedUserInterfaceEnabled; } - - void removeAXID(AccessibilityObject*); - bool isIDinUse(AXID id) const { return m_idsInUse.contains(id); } - AXID platformGenerateAXID() const; - AccessibilityObject* objectFromAXID(AXID id) const { return m_objects.get(id).get(); } - - // Text marker utilities. - static void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&); - static VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&); - - enum AXNotification { - AXCheckedStateChanged, - AXFocusedUIElementChanged, - AXLayoutComplete, - AXLoadComplete, - AXSelectedChildrenChanged, - AXSelectedTextChanged, - AXValueChanged, - AXScrolledToAnchor, - }; - - void postNotification(RenderObject*, AXNotification, bool postToElement); - - protected: - void postPlatformNotification(AccessibilityObject*, AXNotification); - - private: - HashMap<AXID, RefPtr<AccessibilityObject> > m_objects; - HashMap<RenderObject*, AXID> m_renderObjectMapping; - static bool gAccessibilityEnabled; - static bool gAccessibilityEnhancedUserInterfaceEnabled; - - HashSet<AXID> m_idsInUse; - - Timer<AXObjectCache> m_notificationPostTimer; - Vector<pair<RefPtr<AccessibilityObject>, AXNotification> > m_notificationsToPost; - void notificationPostTimerFired(Timer<AXObjectCache>*); - - AXID getAXID(AccessibilityObject*); - bool nodeIsAriaType(Node* node, String role); +class Node; +class Page; +class RenderObject; +class String; +class VisiblePosition; + +struct TextMarkerData { + AXID axID; + Node* node; + int offset; + EAffinity affinity; +}; + +class AXObjectCache : public Noncopyable { +public: + AXObjectCache(); + ~AXObjectCache(); + + static AccessibilityObject* focusedUIElementForPage(const Page*); + + // to be used with render objects + AccessibilityObject* getOrCreate(RenderObject*); + + // used for objects without backing elements + AccessibilityObject* getOrCreate(AccessibilityRole); + + // will only return the AccessibilityObject if it already exists + AccessibilityObject* get(RenderObject*); + + void remove(RenderObject*); + void remove(AXID); + + void detachWrapper(AccessibilityObject*); + void attachWrapper(AccessibilityObject*); + void childrenChanged(RenderObject*); + void selectedChildrenChanged(RenderObject*); + void handleActiveDescendantChanged(RenderObject*); + void handleAriaRoleChanged(RenderObject*); + void handleFocusedUIElementChanged(RenderObject* oldFocusedRenderer, RenderObject* newFocusedRenderer); + void handleScrolledToAnchor(const Node* anchorNode); + + static void enableAccessibility() { gAccessibilityEnabled = true; } + static void enableEnhancedUserInterfaceAccessibility() { gAccessibilityEnhancedUserInterfaceEnabled = true; } + + static bool accessibilityEnabled() { return gAccessibilityEnabled; } + static bool accessibilityEnhancedUserInterfaceEnabled() { return gAccessibilityEnhancedUserInterfaceEnabled; } + + void removeAXID(AccessibilityObject*); + bool isIDinUse(AXID id) const { return m_idsInUse.contains(id); } + AXID platformGenerateAXID() const; + AccessibilityObject* objectFromAXID(AXID id) const { return m_objects.get(id).get(); } + + // Text marker utilities. + static void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&); + static VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&); + + enum AXNotification { + AXActiveDescendantChanged, + AXCheckedStateChanged, + AXFocusedUIElementChanged, + AXLayoutComplete, + AXLoadComplete, + AXSelectedChildrenChanged, + AXSelectedTextChanged, + AXValueChanged, + AXScrolledToAnchor, }; + + void postNotification(RenderObject*, AXNotification, bool postToElement); + +protected: + void postPlatformNotification(AccessibilityObject*, AXNotification); + +private: + HashMap<AXID, RefPtr<AccessibilityObject> > m_objects; + HashMap<RenderObject*, AXID> m_renderObjectMapping; + static bool gAccessibilityEnabled; + static bool gAccessibilityEnhancedUserInterfaceEnabled; + + HashSet<AXID> m_idsInUse; + + Timer<AXObjectCache> m_notificationPostTimer; + Vector<pair<RefPtr<AccessibilityObject>, AXNotification> > m_notificationsToPost; + void notificationPostTimerFired(Timer<AXObjectCache>*); + + AXID getAXID(AccessibilityObject*); + bool nodeIsAriaType(Node*, String role); +}; #if !HAVE(ACCESSIBILITY) - inline void AXObjectCache::handleActiveDescendantChanged(RenderObject*) { } - inline void AXObjectCache::handleAriaRoleChanged(RenderObject*) { } - inline void AXObjectCache::detachWrapper(AccessibilityObject*) { } - inline void AXObjectCache::attachWrapper(AccessibilityObject*) { } - inline void AXObjectCache::selectedChildrenChanged(RenderObject*) { } - inline void AXObjectCache::postNotification(RenderObject*, AXNotification, bool postToElement) { } - inline void AXObjectCache::postPlatformNotification(AccessibilityObject*, AXNotification) { } - inline void AXObjectCache::handleFocusedUIElementChanged(RenderObject*, RenderObject*) { } - inline void AXObjectCache::handleScrolledToAnchor(const Node*) { } +inline void AXObjectCache::handleActiveDescendantChanged(RenderObject*) { } +inline void AXObjectCache::handleAriaRoleChanged(RenderObject*) { } +inline void AXObjectCache::detachWrapper(AccessibilityObject*) { } +inline void AXObjectCache::attachWrapper(AccessibilityObject*) { } +inline void AXObjectCache::selectedChildrenChanged(RenderObject*) { } +inline void AXObjectCache::postNotification(RenderObject*, AXNotification, bool postToElement) { } +inline void AXObjectCache::postPlatformNotification(AccessibilityObject*, AXNotification) { } +inline void AXObjectCache::handleFocusedUIElementChanged(RenderObject*, RenderObject*) { } +inline void AXObjectCache::handleScrolledToAnchor(const Node*) { } #endif } diff --git a/WebCore/accessibility/AccessibilityARIAGrid.cpp b/WebCore/accessibility/AccessibilityARIAGrid.cpp index 69c4512..a0cf77a 100644 --- a/WebCore/accessibility/AccessibilityARIAGrid.cpp +++ b/WebCore/accessibility/AccessibilityARIAGrid.cpp @@ -29,11 +29,11 @@ #include "config.h" #include "AccessibilityARIAGrid.h" +#include "AXObjectCache.h" #include "AccessibilityTableCell.h" #include "AccessibilityTableColumn.h" #include "AccessibilityTableHeaderContainer.h" #include "AccessibilityTableRow.h" -#include "AXObjectCache.h" #include "RenderObject.h" using namespace std; @@ -137,7 +137,7 @@ AccessibilityTableCell* AccessibilityARIAGrid::cellForColumnAndRow(unsigned colu if (column >= columnCount() || row >= rowCount()) return 0; - AccessibilityObject *tableRow = m_rows[row].get(); + AccessibilityObject* tableRow = m_rows[row].get(); if (!tableRow) return 0; diff --git a/WebCore/accessibility/AccessibilityAllInOne.cpp b/WebCore/accessibility/AccessibilityAllInOne.cpp index 04124bd..ada50cd 100755 --- a/WebCore/accessibility/AccessibilityAllInOne.cpp +++ b/WebCore/accessibility/AccessibilityAllInOne.cpp @@ -25,6 +25,7 @@ // This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build.
+#include <AXObjectCache.cpp>
#include <AccessibilityARIAGrid.cpp>
#include <AccessibilityARIAGridCell.cpp>
#include <AccessibilityARIAGridRow.cpp>
@@ -41,4 +42,3 @@ #include <AccessibilityTableColumn.cpp>
#include <AccessibilityTableHeaderContainer.cpp>
#include <AccessibilityTableRow.cpp>
-#include <AXObjectCache.cpp>
diff --git a/WebCore/accessibility/AccessibilityImageMapLink.cpp b/WebCore/accessibility/AccessibilityImageMapLink.cpp index 943122e..06150b9 100644 --- a/WebCore/accessibility/AccessibilityImageMapLink.cpp +++ b/WebCore/accessibility/AccessibilityImageMapLink.cpp @@ -29,8 +29,8 @@ #include "config.h" #include "AccessibilityImageMapLink.h" -#include "AccessibilityRenderObject.h" #include "AXObjectCache.h" +#include "AccessibilityRenderObject.h" #include "Document.h" #include "HTMLNames.h" #include "IntRect.h" @@ -68,6 +68,18 @@ AccessibilityObject* AccessibilityImageMapLink::parentObject() const return m_mapElement->document()->axObjectCache()->getOrCreate(m_mapElement->renderer()); } +AccessibilityRole AccessibilityImageMapLink::roleValue() const +{ + if (!m_areaElement) + return WebCoreLinkRole; + + const AtomicString& ariaRole = m_areaElement->getAttribute(roleAttr); + if (!ariaRole.isEmpty()) + return AccessibilityObject::ariaRoleToWebCoreRole(ariaRole); + + return WebCoreLinkRole; +} + Element* AccessibilityImageMapLink::actionElement() const { return anchorElement(); @@ -134,5 +146,15 @@ IntSize AccessibilityImageMapLink::size() const { return elementRect().size(); } - + +String AccessibilityImageMapLink::stringValueForMSAA() const +{ + return url(); +} + +String AccessibilityImageMapLink::nameForMSAA() const +{ + return accessibilityDescription(); +} + } // namespace WebCore diff --git a/WebCore/accessibility/AccessibilityImageMapLink.h b/WebCore/accessibility/AccessibilityImageMapLink.h index 2c27e46..3024f37 100644 --- a/WebCore/accessibility/AccessibilityImageMapLink.h +++ b/WebCore/accessibility/AccessibilityImageMapLink.h @@ -47,7 +47,7 @@ public: void setHTMLMapElement(HTMLMapElement* element) { m_mapElement = element; } void setParent(AccessibilityObject* parent) { m_parent = parent; } - virtual AccessibilityRole roleValue() const { return WebCoreLinkRole; } + virtual AccessibilityRole roleValue() const; virtual bool accessibilityIsIgnored() const { return false; } virtual bool isEnabled() const { return true; } @@ -56,9 +56,13 @@ public: virtual Element* actionElement() const; virtual KURL url() const; virtual bool isLink() const { return true; } + virtual bool isLinked() const { return true; } virtual String title() const; virtual String accessibilityDescription() const; - + + virtual String stringValueForMSAA() const; + virtual String nameForMSAA() const; + virtual IntSize size() const; virtual IntRect elementRect() const; diff --git a/WebCore/accessibility/AccessibilityList.cpp b/WebCore/accessibility/AccessibilityList.cpp index 95239b0..feceee5 100644 --- a/WebCore/accessibility/AccessibilityList.cpp +++ b/WebCore/accessibility/AccessibilityList.cpp @@ -55,6 +55,10 @@ PassRefPtr<AccessibilityList> AccessibilityList::create(RenderObject* renderer) bool AccessibilityList::accessibilityIsIgnored() const { + // Is the platform interested in the object? + if (accessibilityPlatformIncludesObject() == IgnoreObject) + return true; + // lists don't appear on tiger/leopard on the mac #if ACCESSIBILITY_LISTS return false; @@ -83,7 +87,11 @@ bool AccessibilityList::isOrderedList() const { if (!m_renderer) return false; - + + // ARIA says a directory is like a static table of contents, which sounds like an ordered list. + if (ariaRoleAttribute() == DirectoryRole) + return true; + Node* node = m_renderer->node(); return node && node->hasTagName(olTag); } diff --git a/WebCore/accessibility/AccessibilityList.h b/WebCore/accessibility/AccessibilityList.h index 89befb2..b7265b2 100644 --- a/WebCore/accessibility/AccessibilityList.h +++ b/WebCore/accessibility/AccessibilityList.h @@ -47,7 +47,7 @@ public: static PassRefPtr<AccessibilityList> create(RenderObject*); virtual ~AccessibilityList(); - virtual bool isList() const { return true; }; + virtual bool isList() const { return true; } bool isUnorderedList() const; bool isOrderedList() const; bool isDefinitionList() const; diff --git a/WebCore/accessibility/AccessibilityListBox.cpp b/WebCore/accessibility/AccessibilityListBox.cpp index 1f37481..2a60294 100644 --- a/WebCore/accessibility/AccessibilityListBox.cpp +++ b/WebCore/accessibility/AccessibilityListBox.cpp @@ -31,9 +31,9 @@ #include "AXObjectCache.h" #include "AccessibilityListBoxOption.h" -#include "HitTestResult.h" #include "HTMLNames.h" #include "HTMLSelectElement.h" +#include "HitTestResult.h" #include "RenderListBox.h" #include "RenderObject.h" diff --git a/WebCore/accessibility/AccessibilityListBox.h b/WebCore/accessibility/AccessibilityListBox.h index 3f3352d..ce1abe0 100644 --- a/WebCore/accessibility/AccessibilityListBox.h +++ b/WebCore/accessibility/AccessibilityListBox.h @@ -43,7 +43,7 @@ public: virtual ~AccessibilityListBox(); virtual AccessibilityObject* doAccessibilityHitTest(const IntPoint&) const; - virtual bool isListBox() const { return true; }; + virtual bool isListBox() const { return true; } virtual bool canSetFocusAttribute() const { return true; } virtual bool canSetSelectedChildrenAttribute() const; diff --git a/WebCore/accessibility/AccessibilityListBoxOption.cpp b/WebCore/accessibility/AccessibilityListBoxOption.cpp index 6e3bf98..6a77dac 100644 --- a/WebCore/accessibility/AccessibilityListBoxOption.cpp +++ b/WebCore/accessibility/AccessibilityListBoxOption.cpp @@ -34,12 +34,12 @@ #include "Element.h" #include "HTMLElement.h" #include "HTMLNames.h" -#include "HTMLOptionElement.h" #include "HTMLOptGroupElement.h" +#include "HTMLOptionElement.h" #include "HTMLSelectElement.h" #include "IntRect.h" -#include "RenderObject.h" #include "RenderListBox.h" +#include "RenderObject.h" using namespace std; diff --git a/WebCore/accessibility/AccessibilityListBoxOption.h b/WebCore/accessibility/AccessibilityListBoxOption.h index 933cdeb..f8fd5f0 100644 --- a/WebCore/accessibility/AccessibilityListBoxOption.h +++ b/WebCore/accessibility/AccessibilityListBoxOption.h @@ -63,7 +63,7 @@ public: virtual IntRect elementRect() const; virtual IntSize size() const; virtual AccessibilityObject* parentObject() const; - bool isListBoxOption() const { return true; }; + bool isListBoxOption() const { return true; } private: HTMLElement* m_optionElement; diff --git a/WebCore/accessibility/AccessibilityMediaControls.cpp b/WebCore/accessibility/AccessibilityMediaControls.cpp index 7200de9..6151840 100644 --- a/WebCore/accessibility/AccessibilityMediaControls.cpp +++ b/WebCore/accessibility/AccessibilityMediaControls.cpp @@ -113,6 +113,8 @@ String AccessibilityMediaControl::controlTypeName() const DEFINE_STATIC_LOCAL(const String, mediaStatusDisplayName, ("StatusDisplay")); DEFINE_STATIC_LOCAL(const String, mediaCurrentTimeDisplay, ("CurrentTimeDisplay")); DEFINE_STATIC_LOCAL(const String, mediaTimeRemainingDisplay, ("TimeRemainingDisplay")); + DEFINE_STATIC_LOCAL(const String, mediaShowClosedCaptionsButtonName, ("ShowClosedCaptionsButton")); + DEFINE_STATIC_LOCAL(const String, mediaHideClosedCaptionsButtonName, ("HideClosedCaptionsButton")); switch (controlType()) { case MediaFullscreenButton: @@ -139,6 +141,10 @@ String AccessibilityMediaControl::controlTypeName() const return mediaCurrentTimeDisplay; case MediaTimeRemainingDisplay: return mediaTimeRemainingDisplay; + case MediaShowClosedCaptionsButton: + return mediaShowClosedCaptionsButtonName; + case MediaHideClosedCaptionsButton: + return mediaHideClosedCaptionsButtonName; default: break; @@ -187,6 +193,8 @@ AccessibilityRole AccessibilityMediaControl::roleValue() const case MediaReturnToRealtimeButton: case MediaUnMuteButton: case MediaPauseButton: + case MediaShowClosedCaptionsButton: + case MediaHideClosedCaptionsButton: return ButtonRole; case MediaStatusDisplay: diff --git a/WebCore/accessibility/AccessibilityMediaControls.h b/WebCore/accessibility/AccessibilityMediaControls.h index 9b306fd..6f51b2c 100644 --- a/WebCore/accessibility/AccessibilityMediaControls.h +++ b/WebCore/accessibility/AccessibilityMediaControls.h @@ -37,77 +37,77 @@ namespace WebCore { - class AccessibilityMediaControl : public AccessibilityRenderObject { +class AccessibilityMediaControl : public AccessibilityRenderObject { - public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); - virtual ~AccessibilityMediaControl() { } +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaControl() { } - virtual AccessibilityRole roleValue() const; - virtual bool accessibilityIsIgnored() const; + virtual AccessibilityRole roleValue() const; + virtual bool accessibilityIsIgnored() const; - virtual String title() const; - virtual String accessibilityDescription() const; - virtual String helpText() const; + virtual String title() const; + virtual String accessibilityDescription() const; + virtual String helpText() const; - protected: - AccessibilityMediaControl(RenderObject*); - MediaControlElementType controlType() const; - String controlTypeName() const; - }; +protected: + AccessibilityMediaControl(RenderObject*); + MediaControlElementType controlType() const; + String controlTypeName() const; +}; - class AccessibilityMediaTimeline : public AccessibilitySlider { +class AccessibilityMediaTimeline : public AccessibilitySlider { - public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); - virtual ~AccessibilityMediaTimeline() { } +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaTimeline() { } - virtual bool isMediaTimeline() const { return true; } + virtual bool isMediaTimeline() const { return true; } - virtual String helpText() const; - virtual String valueDescription() const; - const AtomicString& getAttribute(const QualifiedName& attribute) const; + virtual String helpText() const; + virtual String valueDescription() const; + const AtomicString& getAttribute(const QualifiedName& attribute) const; - private: - AccessibilityMediaTimeline(RenderObject*); - }; +private: + AccessibilityMediaTimeline(RenderObject*); +}; - class AccessibilityMediaControlsContainer : public AccessibilityMediaControl { +class AccessibilityMediaControlsContainer : public AccessibilityMediaControl { - public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); - virtual ~AccessibilityMediaControlsContainer() { } +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaControlsContainer() { } - virtual AccessibilityRole roleValue() const { return ToolbarRole; } - virtual bool accessibilityIsIgnored() const { return false; } + virtual AccessibilityRole roleValue() const { return ToolbarRole; } + virtual bool accessibilityIsIgnored() const { return false; } - virtual String helpText() const; - virtual String accessibilityDescription() const; + virtual String helpText() const; + virtual String accessibilityDescription() const; - private: - AccessibilityMediaControlsContainer(RenderObject*); - bool controllingVideoElement() const; - const String elementTypeName() const; - }; +private: + AccessibilityMediaControlsContainer(RenderObject*); + bool controllingVideoElement() const; + const String elementTypeName() const; +}; - class AccessibilityMediaTimeDisplay : public AccessibilityMediaControl { +class AccessibilityMediaTimeDisplay : public AccessibilityMediaControl { - public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); - virtual ~AccessibilityMediaTimeDisplay() { } +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaTimeDisplay() { } - virtual AccessibilityRole roleValue() const { return StaticTextRole; } - virtual bool accessibilityIsIgnored() const; + virtual AccessibilityRole roleValue() const { return StaticTextRole; } + virtual bool accessibilityIsIgnored() const; - virtual String stringValue() const; - virtual String accessibilityDescription() const; + virtual String stringValue() const; + virtual String accessibilityDescription() const; - private: - AccessibilityMediaTimeDisplay(RenderObject*); - }; +private: + AccessibilityMediaTimeDisplay(RenderObject*); +}; } // namespace WebCore diff --git a/WebCore/accessibility/AccessibilityObject.cpp b/WebCore/accessibility/AccessibilityObject.cpp index d7093e4..585e4cb 100644 --- a/WebCore/accessibility/AccessibilityObject.cpp +++ b/WebCore/accessibility/AccessibilityObject.cpp @@ -29,8 +29,8 @@ #include "config.h" #include "AccessibilityObject.h" -#include "AccessibilityRenderObject.h" #include "AXObjectCache.h" +#include "AccessibilityRenderObject.h" #include "CharacterNames.h" #include "FloatRect.h" #include "FocusController.h" @@ -85,8 +85,9 @@ void AccessibilityObject::detach() AccessibilityObject* AccessibilityObject::parentObjectUnignored() const { AccessibilityObject* parent; - for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) - ; + for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { + } + return parent; } @@ -275,7 +276,7 @@ VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const Vi VisiblePosition startPosition = startOfLine(nextVisiblePos); // fetch for a valid line start position - if (startPosition.isNull() ) { + if (startPosition.isNull()) { startPosition = visiblePos; nextVisiblePos = nextVisiblePos.next(); } else @@ -381,9 +382,8 @@ static bool replacedNodeNeedsCharacter(Node* replacedNode) { // we should always be given a rendered node and a replaced node, but be safe // replaced nodes are either attachments (widgets) or images - if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) { + if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) return false; - } // create an AX object, but skip it if it is not supposed to be seen AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer()); @@ -445,7 +445,7 @@ String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionR RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length() != 0) { + if (it.length()) { // Add a textual representation for list marker text String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start); if (!listMarkerText.isEmpty()) @@ -459,9 +459,8 @@ String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionR ASSERT(node == it.range()->endContainer(exception)); int offset = it.range()->startOffset(exception); - if (replacedNodeNeedsCharacter(node->childNode(offset))) { + if (replacedNodeNeedsCharacter(node->childNode(offset))) resultVector.append(objectReplacementCharacter); - } } } @@ -478,9 +477,9 @@ int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRang RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length() != 0) { + if (it.length()) length += it.length(); - } else { + else { // locate the node and starting offset for this replaced range int exception = 0; Node* node = it.range()->startContainer(exception); @@ -754,6 +753,53 @@ AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer); } +void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result) +{ + AccessibilityChildrenVector axChildren = children(); + unsigned count = axChildren.size(); + for (unsigned k = 0; k < count; ++k) { + AccessibilityObject* obj = axChildren[k].get(); + + // Add tree items as the rows. + if (obj->roleValue() == TreeItemRole) + result.append(obj); + + // Now see if this item also has rows hiding inside of it. + obj->ariaTreeRows(result); + } +} + +void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result) +{ + // The ARIA tree item content are the item that are not other tree items or their containing groups. + AccessibilityChildrenVector axChildren = children(); + unsigned count = axChildren.size(); + for (unsigned k = 0; k < count; ++k) { + AccessibilityObject* obj = axChildren[k].get(); + AccessibilityRole role = obj->roleValue(); + if (role == TreeItemRole || role == GroupRole) + continue; + + result.append(obj); + } +} + +void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result) +{ + AccessibilityChildrenVector axChildren = children(); + unsigned count = axChildren.size(); + for (unsigned k = 0; k < count; ++k) { + AccessibilityObject* obj = axChildren[k].get(); + + // Add tree items as the rows. + if (obj->roleValue() == TreeItemRole) + result.append(obj); + // If it's not a tree item, then descend into the group to find more tree items. + else + obj->ariaTreeRows(result); + } +} + const String& AccessibilityObject::actionVerb() const { // FIXME: Need to add verbs for select elements. @@ -766,20 +812,20 @@ const String& AccessibilityObject::actionVerb() const DEFINE_STATIC_LOCAL(const String, noAction, ()); switch (roleValue()) { - case ButtonRole: - return buttonAction; - case TextFieldRole: - case TextAreaRole: - return textFieldAction; - case RadioButtonRole: - return radioButtonAction; - case CheckBoxRole: - return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; - case LinkRole: - case WebCoreLinkRole: - return linkAction; - default: - return noAction; + case ButtonRole: + return buttonAction; + case TextFieldRole: + case TextAreaRole: + return textFieldAction; + case RadioButtonRole: + return radioButtonAction; + case CheckBoxRole: + return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; + case LinkRole: + case WebCoreLinkRole: + return linkAction; + default: + return noAction; } } @@ -794,6 +840,93 @@ AccessibilityOrientation AccessibilityObject::orientation() const // A tie goes to horizontal. return AccessibilityOrientationHorizontal; +} + +typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; + +struct RoleEntry { + String ariaRole; + AccessibilityRole webcoreRole; +}; + +static ARIARoleMap* createARIARoleMap() +{ + const RoleEntry roles[] = { + { "alert", ApplicationAlertRole }, + { "alertdialog", ApplicationAlertDialogRole }, + { "application", LandmarkApplicationRole }, + { "article", DocumentArticleRole }, + { "banner", LandmarkBannerRole }, + { "button", ButtonRole }, + { "checkbox", CheckBoxRole }, + { "complementary", LandmarkComplementaryRole }, + { "contentinfo", LandmarkContentInfoRole }, + { "dialog", ApplicationDialogRole }, + { "directory", DirectoryRole }, + { "grid", TableRole }, + { "gridcell", CellRole }, + { "columnheader", ColumnHeaderRole }, + { "combobox", ComboBoxRole }, + { "definition", DefinitionListDefinitionRole }, + { "document", DocumentRole }, + { "rowheader", RowHeaderRole }, + { "group", GroupRole }, + { "heading", HeadingRole }, + { "img", ImageRole }, + { "link", WebCoreLinkRole }, + { "list", ListRole }, + { "listitem", GroupRole }, + { "listbox", ListBoxRole }, + { "log", ApplicationLogRole }, + // "option" isn't here because it may map to different roles depending on the parent element's role + { "main", LandmarkMainRole }, + { "marquee", ApplicationMarqueeRole }, + { "math", DocumentMathRole }, + { "menu", MenuRole }, + { "menubar", GroupRole }, + // "menuitem" isn't here because it may map to different roles depending on the parent element's role + { "menuitemcheckbox", MenuItemRole }, + { "menuitemradio", MenuItemRole }, + { "note", DocumentNoteRole }, + { "navigation", LandmarkNavigationRole }, + { "option", ListBoxOptionRole }, + { "presentation", IgnoredRole }, + { "progressbar", ProgressIndicatorRole }, + { "radio", RadioButtonRole }, + { "radiogroup", RadioGroupRole }, + { "region", DocumentRegionRole }, + { "row", RowRole }, + { "range", SliderRole }, + { "scrollbar", ScrollBarRole }, + { "search", LandmarkSearchRole }, + { "separator", SplitterRole }, + { "slider", SliderRole }, + { "spinbutton", ProgressIndicatorRole }, + { "status", ApplicationStatusRole }, + { "tab", TabRole }, + { "tablist", TabListRole }, + { "tabpanel", TabPanelRole }, + { "text", StaticTextRole }, + { "textbox", TextAreaRole }, + { "timer", ApplicationTimerRole }, + { "toolbar", ToolbarRole }, + { "tooltip", UserInterfaceTooltipRole }, + { "tree", TreeRole }, + { "treeitem", TreeItemRole } + }; + ARIARoleMap* roleMap = new ARIARoleMap; + + const unsigned numRoles = sizeof(roles) / sizeof(roles[0]); + for (unsigned i = 0; i < numRoles; ++i) + roleMap->set(roles[i].ariaRole, roles[i].webcoreRole); + return roleMap; +} + +AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) +{ + ASSERT(!value.isEmpty()); + static const ARIARoleMap* roleMap = createARIARoleMap(); + return roleMap->get(value); } } // namespace WebCore diff --git a/WebCore/accessibility/AccessibilityObject.h b/WebCore/accessibility/AccessibilityObject.h index 8fc40e8..8e46311 100644 --- a/WebCore/accessibility/AccessibilityObject.h +++ b/WebCore/accessibility/AccessibilityObject.h @@ -161,6 +161,12 @@ enum AccessibilityRole { AnnotationRole, SliderThumbRole, IgnoredRole, + TabRole, + TabListRole, + TabPanelRole, + TreeRole, + TreeItemRole, + DirectoryRole, // ARIA Grouping roles LandmarkApplicationRole, @@ -171,6 +177,9 @@ enum AccessibilityRole { LandmarkNavigationRole, LandmarkSearchRole, + ApplicationAlertRole, + ApplicationAlertDialogRole, + ApplicationDialogRole, ApplicationLogRole, ApplicationMarqueeRole, ApplicationStatusRole, @@ -178,6 +187,7 @@ enum AccessibilityRole { DocumentRole, DocumentArticleRole, + DocumentMathRole, DocumentNoteRole, DocumentRegionRole, @@ -225,7 +235,7 @@ struct PlainTextRange { , length(l) { } - bool isNull() const { return start == 0 && length == 0; } + bool isNull() const { return !start && !length; } }; class AccessibilityObject : public RefCounted<AccessibilityObject> { @@ -237,64 +247,75 @@ public: typedef Vector<RefPtr<AccessibilityObject> > AccessibilityChildrenVector; - virtual bool isAccessibilityRenderObject() const { return false; }; - virtual bool isAnchor() const { return false; }; - virtual bool isAttachment() const { return false; }; - virtual bool isHeading() const { return false; }; - virtual bool isLink() const { return false; }; - virtual bool isImage() const { return false; }; - virtual bool isNativeImage() const { return false; }; - virtual bool isImageButton() const { return false; }; - virtual bool isPasswordField() const { return false; }; - virtual bool isTextControl() const { return false; }; - virtual bool isNativeTextControl() const { return false; }; - virtual bool isWebArea() const { return false; }; - virtual bool isCheckboxOrRadio() const { return false; }; - virtual bool isListBox() const { return roleValue() == ListBoxRole; }; + virtual bool isAccessibilityRenderObject() const { return false; } + virtual bool isAnchor() const { return false; } + virtual bool isAttachment() const { return false; } + virtual bool isHeading() const { return false; } + virtual bool isLink() const { return false; } + virtual bool isImage() const { return false; } + virtual bool isNativeImage() const { return false; } + virtual bool isImageButton() const { return false; } + virtual bool isPasswordField() const { return false; } + virtual bool isTextControl() const { return false; } + virtual bool isNativeTextControl() const { return false; } + virtual bool isWebArea() const { return false; } + virtual bool isCheckboxOrRadio() const { return false; } + virtual bool isListBox() const { return roleValue() == ListBoxRole; } virtual bool isMediaTimeline() const { return false; } virtual bool isMenuRelated() const { return false; } virtual bool isMenu() const { return false; } virtual bool isMenuBar() const { return false; } virtual bool isMenuButton() const { return false; } virtual bool isMenuItem() const { return false; } - virtual bool isFileUploadButton() const { return false; }; + virtual bool isFileUploadButton() const { return false; } virtual bool isInputImage() const { return false; } - virtual bool isProgressIndicator() const { return false; }; - virtual bool isSlider() const { return false; }; - virtual bool isControl() const { return false; }; - virtual bool isList() const { return false; }; - virtual bool isDataTable() const { return false; }; - virtual bool isTableRow() const { return false; }; - virtual bool isTableColumn() const { return false; }; - virtual bool isTableCell() const { return false; }; - virtual bool isFieldset() const { return false; }; - virtual bool isGroup() const { return false; }; + virtual bool isProgressIndicator() const { return false; } + virtual bool isSlider() const { return false; } + virtual bool isControl() const { return false; } + virtual bool isList() const { return false; } + virtual bool isDataTable() const { return false; } + virtual bool isTableRow() const { return false; } + virtual bool isTableColumn() const { return false; } + 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; } + bool isComboBox() const { return roleValue() == ComboBoxRole; } + bool isTree() const { return roleValue() == TreeRole; } + bool isTreeItem() const { return roleValue() == TreeItemRole; } + bool isScrollbar() const { return roleValue() == ScrollBarRole; } + bool isButton() const { return roleValue() == ButtonRole; } - virtual bool isChecked() const { return false; }; - virtual bool isEnabled() const { return false; }; - virtual bool isSelected() const { return false; }; - virtual bool isFocused() const { return false; }; - virtual bool isHovered() const { return false; }; - virtual bool isIndeterminate() const { return false; }; - virtual bool isLoaded() const { return false; }; - virtual bool isMultiSelect() const { return false; }; - virtual bool isOffScreen() const { return false; }; - virtual bool isPressed() const { return false; }; - virtual bool isReadOnly() const { return false; }; - virtual bool isVisited() const { return false; }; - virtual bool isRequired() const { return false; }; - - virtual bool canSetFocusAttribute() const { return false; }; - virtual bool canSetTextRangeAttributes() const { return false; }; - virtual bool canSetValueAttribute() const { return false; }; + virtual bool isChecked() const { return false; } + virtual bool isEnabled() const { return false; } + virtual bool isSelected() const { return false; } + virtual bool isFocused() const { return false; } + virtual bool isHovered() const { return false; } + virtual bool isIndeterminate() const { return false; } + virtual bool isLoaded() const { return false; } + virtual bool isMultiSelect() const { return false; } + virtual bool isOffScreen() const { return false; } + virtual bool isPressed() const { return false; } + virtual bool isReadOnly() const { return false; } + virtual bool isVisited() const { return false; } + virtual bool isRequired() const { return false; } + virtual bool isLinked() const { return false; } + virtual bool isExpanded() const { return false; } + virtual void setIsExpanded(bool) { } + + virtual bool canSetFocusAttribute() const { return false; } + virtual bool canSetTextRangeAttributes() const { return false; } + virtual bool canSetValueAttribute() const { return false; } virtual bool canSetSelectedAttribute() const { return false; } virtual bool canSetSelectedChildrenAttribute() const { return false; } + virtual bool canSetExpandedAttribute() const { return false; } - virtual bool hasIntValue() const { return false; }; + virtual bool hasIntValue() const { return false; } - bool accessibilityShouldUseUniqueId() const { return true; }; - virtual bool accessibilityIsIgnored() const { return true; }; + bool accessibilityShouldUseUniqueId() const { return true; } + virtual bool accessibilityIsIgnored() const { return true; } virtual int headingLevel() const { return 0; } virtual int intValue() const { return 0; } @@ -303,9 +324,21 @@ 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); + virtual bool supportsARIAOwns() const { return false; } + virtual void ariaOwnsElements(AccessibilityChildrenVector&) const { } + virtual bool supportsARIAFlowTo() const { return false; } + virtual void ariaFlowToElements(AccessibilityChildrenVector&) const { } + + // ARIA drag and drop + virtual bool supportsARIADropping() { return false; } + virtual bool supportsARIADragging() { return false; } + virtual bool isARIAGrabbed() { return false; } + virtual void setARIAGrabbed(bool) { } + virtual void determineARIADropEffects(Vector<String>&) { } virtual AccessibilityObject* doAccessibilityHitTest(const IntPoint&) const { return 0; } virtual AccessibilityObject* focusedUIElement() const { return 0; } @@ -331,7 +364,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(); } @@ -370,20 +402,22 @@ public: virtual FrameView* topDocumentFrameView() const { return 0; } virtual FrameView* documentFrameView() const; virtual String language() const; - + virtual unsigned hierarchicalLevel() const { return 0; } + virtual void setFocused(bool) { } virtual void setSelectedText(const String&) { } virtual void setSelectedTextRange(const PlainTextRange&) { } virtual void setValue(const String&) { } virtual void setSelected(bool) { } - + virtual void setSelectedRows(AccessibilityChildrenVector&) { } + virtual void makeRangeVisible(const PlainTextRange&) { } virtual bool press() const; bool performDefaultAction() const { return press(); } - + virtual AccessibilityOrientation orientation() const; - virtual void increment() { }; - virtual void decrement() { }; + virtual void increment() { } + virtual void decrement() { } virtual void childrenChanged() { } virtual const AccessibilityChildrenVector& children() { return m_children; } @@ -392,10 +426,13 @@ 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() { } + static AccessibilityRole ariaRoleToWebCoreRole(const String&); + virtual VisiblePositionRange visiblePositionRange() const { return VisiblePositionRange(); } virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); } @@ -446,6 +483,18 @@ public: unsigned doAXLineForIndex(unsigned); + virtual String stringValueForMSAA() const { return String(); } + virtual String stringRoleForMSAA() const { return String(); } + virtual String nameForMSAA() const { return String(); } + virtual String descriptionForMSAA() const { return String(); } + + // Used by an ARIA tree to get all its rows. + void ariaTreeRows(AccessibilityChildrenVector&); + // Used by an ARIA tree item to get all of its direct rows that it can disclose. + void ariaTreeItemDisclosedRows(AccessibilityChildrenVector&); + // Used by an ARIA tree item to get only its content, and not its child tree items and groups. + void ariaTreeItemContent(AccessibilityChildrenVector&); + #if HAVE(ACCESSIBILITY) #if PLATFORM(GTK) AccessibilityObjectWrapper* wrapper() const; diff --git a/WebCore/accessibility/AccessibilityRenderObject.cpp b/WebCore/accessibility/AccessibilityRenderObject.cpp index 4c50b9a..c57dd92 100644 --- a/WebCore/accessibility/AccessibilityRenderObject.cpp +++ b/WebCore/accessibility/AccessibilityRenderObject.cpp @@ -30,8 +30,8 @@ #include "AccessibilityRenderObject.h" #include "AXObjectCache.h" -#include "AccessibilityListBox.h" #include "AccessibilityImageMapLink.h" +#include "AccessibilityListBox.h" #include "CharacterNames.h" #include "EventNames.h" #include "FloatRect.h" @@ -84,6 +84,7 @@ AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer) : AccessibilityObject() , m_renderer(renderer) , m_ariaRole(UnknownRole) + , m_childrenDirty(false) { updateAccessibilityRole(); #ifndef NDEBUG @@ -166,7 +167,7 @@ AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const if (!m_renderer) return 0; - RenderObject *parent = m_renderer->parent(); + RenderObject* parent = m_renderer->parent(); if (!parent) return 0; @@ -178,7 +179,7 @@ AccessibilityObject* AccessibilityRenderObject::parentObject() const if (!m_renderer) return 0; - RenderObject *parent = m_renderer->parent(); + RenderObject* parent = m_renderer->parent(); if (!parent) return 0; @@ -296,10 +297,10 @@ bool AccessibilityRenderObject::isSlider() const bool AccessibilityRenderObject::isMenuRelated() const { AccessibilityRole role = roleValue(); - return role == MenuRole || - role == MenuBarRole || - role == MenuButtonRole || - role == MenuItemRole; + return role == MenuRole + || role == MenuBarRole + || role == MenuButtonRole + || role == MenuItemRole; } bool AccessibilityRenderObject::isMenu() const @@ -505,6 +506,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 { @@ -693,6 +712,40 @@ String AccessibilityRenderObject::helpText() const return String(); } +unsigned AccessibilityRenderObject::hierarchicalLevel() const +{ + if (!m_renderer) + return 0; + + Node* node = m_renderer->node(); + if (!node || !node->isElementNode()) + return 0; + Element* element = static_cast<Element*>(node); + String ariaLevel = element->getAttribute(aria_levelAttr); + if (!ariaLevel.isEmpty()) + return ariaLevel.toInt(); + + // Only tree item will calculate its level through the DOM currently. + if (roleValue() != TreeItemRole) + return 0; + + // Hierarchy leveling starts at 0. + // We measure tree hierarchy by the number of groups that the item is within. + unsigned level = 0; + AccessibilityObject* parent = parentObject(); + while (parent) { + AccessibilityRole parentRole = parent->roleValue(); + if (parentRole == GroupRole) + level++; + else if (parentRole == TreeRole) + break; + + parent = parent->parentObject(); + } + + return level; +} + String AccessibilityRenderObject::language() const { if (!m_renderer) @@ -780,7 +833,7 @@ String AccessibilityRenderObject::valueDescription() const float AccessibilityRenderObject::valueForRange() const { - if (!isProgressIndicator() && !isSlider()) + if (!isProgressIndicator() && !isSlider() && !isScrollbar()) return 0.0f; return getAttribute(aria_valuenowAttr).toFloat(); @@ -807,6 +860,9 @@ String AccessibilityRenderObject::stringValue() const if (!m_renderer || isPasswordField()) return String(); + if (ariaRoleAttribute() == StaticTextRole) + return text(); + if (m_renderer->isText()) return textUnderElement(); @@ -864,56 +920,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 +1057,8 @@ String AccessibilityRenderObject::title() const || ariaRole == MenuItemRole || ariaRole == MenuButtonRole || ariaRole == RadioButtonRole + || ariaRole == CheckBoxRole + || ariaRole == TabRole || isHeading()) return textUnderElement(); @@ -1001,11 +1070,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 @@ -1032,7 +1100,7 @@ String AccessibilityRenderObject::accessibilityDescription() const } if (isWebArea()) { - Document *document = m_renderer->document(); + Document* document = m_renderer->document(); Node* owner = document->ownerElement(); if (owner) { if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { @@ -1048,12 +1116,7 @@ String AccessibilityRenderObject::accessibilityDescription() const if (owner && owner->isHTMLElement()) return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); } - - if (roleValue() == DefinitionListTermRole) - return AXDefinitionListTermText(); - if (roleValue() == DefinitionListDefinitionRole) - return AXDefinitionListDefinitionText(); - + return String(); } @@ -1198,6 +1261,8 @@ void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildren // or an internal anchor connection void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const { + ariaFlowToElements(linkedUIElements); + if (isAnchor()) { AccessibilityObject* linkedAXElement = internalLinkElement(); if (linkedAXElement) @@ -1208,6 +1273,71 @@ void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& li addRadioButtonGroupMembers(linkedUIElements); } +bool AccessibilityRenderObject::hasTextAlternative() const +{ + // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should + // override the "label" element association. + if (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).string().isEmpty()) + return true; + + return false; +} + +bool AccessibilityRenderObject::supportsARIAFlowTo() const +{ + return !getAttribute(aria_flowtoAttr).string().isEmpty(); +} + +void AccessibilityRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const +{ + Vector<Element*> elements; + elementsFromAttribute(elements, aria_flowtoAttr); + + AXObjectCache* cache = axObjectCache(); + unsigned count = elements.size(); + for (unsigned k = 0; k < count; ++k) { + Element* element = elements[k]; + AccessibilityObject* flowToElement = cache->getOrCreate(element->renderer()); + if (flowToElement) + flowTo.append(flowToElement); + } + +} + +bool AccessibilityRenderObject::supportsARIADropping() +{ + const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr).string(); + return !dropEffect.isEmpty(); +} + +bool AccessibilityRenderObject::supportsARIADragging() +{ + const AtomicString& grabbed = getAttribute(aria_grabbedAttr).string(); + return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "false"); +} + +bool AccessibilityRenderObject::isARIAGrabbed() +{ + return elementAttributeValue(aria_grabbedAttr); +} + +void AccessibilityRenderObject::setARIAGrabbed(bool grabbed) +{ + setElementAttributeValue(aria_grabbedAttr, grabbed); +} + +void AccessibilityRenderObject::determineARIADropEffects(Vector<String>& effects) +{ + String dropEffects = getAttribute(aria_dropeffectAttr).string(); + if (dropEffects.isEmpty()) { + effects.clear(); + return; + } + + dropEffects.replace('\n', ' '); + dropEffects.split(' ', effects); +} + bool AccessibilityRenderObject::exposesTitleUIElement() const { if (!isControl()) @@ -1217,6 +1347,9 @@ bool AccessibilityRenderObject::exposesTitleUIElement() const if (isCheckboxOrRadio() && getAttribute(titleAttr).isEmpty()) return false; + if (hasTextAlternative()) + return false; + return true; } @@ -1256,9 +1389,19 @@ bool AccessibilityRenderObject::ariaIsHidden() const return false; } +bool AccessibilityRenderObject::isDescendantOfBarrenParent() const +{ + for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { + if (!object->canHaveChildren()) + return true; + } + + return false; +} + bool AccessibilityRenderObject::accessibilityIsIgnored() const { - // is the platform is interested in this object? + // Is the platform interested in this object? AccessibilityObjectPlatformInclusion decision = accessibilityPlatformIncludesObject(); if (decision == IncludeObject) return false; @@ -1276,6 +1419,10 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const if (isPresentationalChildOfAriaRole()) return true; + // If this element is within a parent that cannot have children, it should not be exposed. + if (isDescendantOfBarrenParent()) + return true; + if (roleValue() == IgnoredRole) return true; @@ -1288,7 +1435,7 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const // find out if this element is inside of a label element. // if so, it may be ignored because it's the label for a checkbox or radio button AccessibilityObject* controlObject = correspondingControlForLabelElement(); - if (controlObject && !controlObject->exposesTitleUIElement()) + if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio()) return true; AccessibilityRole ariaRole = ariaRoleAttribute(); @@ -1300,8 +1447,8 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const // NOTE: BRs always have text boxes now, so the text box check here can be removed if (m_renderer->isText()) { // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level - if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole || - parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole) + if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole + || parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole) return true; RenderText* renderText = toRenderText(m_renderer); if (m_renderer->isBR() || !renderText->firstTextBox()) @@ -1388,6 +1535,10 @@ int AccessibilityRenderObject::layoutCount() const String AccessibilityRenderObject::text() const { + // If this is a user defined static text, use the accessible name computation. + if (ariaRoleAttribute() == StaticTextRole) + return accessibilityDescription(); + if (!isTextControl() || isPasswordField()) return String(); @@ -1545,6 +1696,46 @@ bool AccessibilityRenderObject::isVisited() const return m_renderer->style()->pseudoState() == PseudoVisited; } +bool AccessibilityRenderObject::isExpanded() const +{ + if (equalIgnoringCase(getAttribute(aria_expandedAttr).string(), "true")) + return true; + + return false; +} + +void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& attributeName, bool value) +{ + if (!m_renderer) + return; + + Node* node = m_renderer->node(); + if (!node || !node->isElementNode()) + return; + + Element* element = static_cast<Element*>(node); + element->setAttribute(attributeName, (value) ? "true" : "false"); +} + +bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) +{ + if (!m_renderer) + return false; + + return equalIgnoringCase(getAttribute(attributeName).string(), "true"); +} + +void AccessibilityRenderObject::setIsExpanded(bool isExpanded) +{ + // Combo boxes and tree items can be expanded (in different ways on different platforms). + // That action translates into setting the aria-expanded attribute to true. + AccessibilityRole role = roleValue(); + if (role != ComboBoxRole && role != TreeItemRole) + return; + + setElementAttributeValue(aria_expandedAttr, isExpanded); +} + bool AccessibilityRenderObject::isRequired() const { if (equalIgnoringCase(getAttribute(aria_requiredAttr).string(), "true")) @@ -1562,9 +1753,56 @@ bool AccessibilityRenderObject::isSelected() const if (!node) return false; + String ariaSelected = getAttribute(aria_selectedAttr).string(); + if (equalIgnoringCase(ariaSelected, "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) @@ -1580,8 +1818,8 @@ bool AccessibilityRenderObject::isFocused() const // A web area is represented by the Document node in the DOM tree, which isn't focusable. // Check instead if the frame's selection controller is focused - if (focusedNode == m_renderer->node() || - (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive())) + if (focusedNode == m_renderer->node() + || (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive())) return true; return false; @@ -1613,6 +1851,26 @@ void AccessibilityRenderObject::changeValueByPercent(float percentChange) axObjectCache()->postNotification(m_renderer, AXObjectCache::AXValueChanged, true); } +void AccessibilityRenderObject::setSelected(bool enabled) +{ + setElementAttributeValue(aria_selectedAttr, enabled); +} + +void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) +{ + // Setting selected rows only works on trees for now. + if (roleValue() != TreeRole) + return; + + bool isMultiselectable = elementAttributeValue(aria_multiselectableAttr); + unsigned count = selectedRows.size(); + if (count > 1 && !isMultiselectable) + count = 1; + + for (unsigned k = 0; k < count; ++k) + selectedRows[k]->setSelected(true); +} + void AccessibilityRenderObject::setValue(const String& string) { if (!m_renderer) @@ -1632,6 +1890,29 @@ void AccessibilityRenderObject::setValue(const String& string) } } +void AccessibilityRenderObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const +{ + Vector<Element*> elements; + elementsFromAttribute(elements, aria_ownsAttr); + + unsigned count = elements.size(); + for (unsigned k = 0; k < count; ++k) { + RenderObject* render = elements[k]->renderer(); + AccessibilityObject* obj = axObjectCache()->getOrCreate(render); + if (obj) + axObjects.append(obj); + } +} + +bool AccessibilityRenderObject::supportsARIAOwns() const +{ + if (!m_renderer) + return false; + const AtomicString& ariaOwns = getAttribute(aria_ownsAttr).string(); + + return !ariaOwns.isEmpty(); +} + bool AccessibilityRenderObject::isEnabled() const { ASSERT(m_renderer); @@ -1777,7 +2058,7 @@ VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const { - if (lineCount == 0 || !m_renderer) + if (!lineCount || !m_renderer) return VisiblePositionRange(); // iterate over the lines @@ -1785,7 +2066,7 @@ VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsi // last offset of the last line VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForCoordinates(0, 0); VisiblePosition savedVisiblePos; - while (--lineCount != 0) { + while (--lineCount) { savedVisiblePos = visiblePos; visiblePos = nextLinePosition(visiblePos, 0); if (visiblePos.isNull() || visiblePos == savedVisiblePos) @@ -1900,9 +2181,8 @@ void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePos return; // make selection and tell the document to use it. if it's zero length, then move to that position - if (range.start == range.end) { + if (range.start == range.end) m_renderer->document()->frame()->selection()->moveTo(range.start, true); - } else { VisibleSelection newSelection = VisibleSelection(range.start, range.end); m_renderer->document()->frame()->selection()->setSelection(newSelection); @@ -1998,7 +2278,7 @@ PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) // iterate to the specified line VisiblePosition visiblePos = visiblePositionForIndex(0); VisiblePosition savedVisiblePos; - for (unsigned lineCount = lineNumber; lineCount != 0; lineCount -= 1) { + for (unsigned lineCount = lineNumber; lineCount; lineCount -= 1) { savedVisiblePos = visiblePos; visiblePos = nextLinePosition(visiblePos, 0); if (visiblePos.isNull() || visiblePos == savedVisiblePos) @@ -2053,8 +2333,8 @@ String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range if (isPasswordField()) return String(); - if (range.length == 0) - return ""; + if (!range.length) + return String(); if (!isTextControl()) return String(); @@ -2081,7 +2361,7 @@ AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTM if (!area) return 0; - HTMLMapElement *map = static_cast<HTMLMapElement*>(area->parent()); + HTMLMapElement* map = static_cast<HTMLMapElement*>(area->parent()); AccessibilityObject* parent = accessibilityParentForImageMap(map); if (!parent) return 0; @@ -2159,14 +2439,14 @@ bool AccessibilityRenderObject::shouldFocusActiveDescendant() const case ProgressIndicatorRole: case ToolbarRole: case OutlineRole: + case TreeRole: + case GridRole: /* FIXME: replace these with actual roles when they are added to AccessibilityRole composite alert alertdialog - grid status timer - tree */ return true; default: @@ -2176,19 +2456,22 @@ bool AccessibilityRenderObject::shouldFocusActiveDescendant() const AccessibilityObject* AccessibilityRenderObject::activeDescendant() const { - if (renderer()->node() && !renderer()->node()->isElementNode()) + if (!m_renderer) return 0; - Element* element = static_cast<Element*>(renderer()->node()); + + if (m_renderer->node() && !m_renderer->node()->isElementNode()) + return 0; + Element* element = static_cast<Element*>(m_renderer->node()); String activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr).string(); if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) return 0; - Element* target = renderer()->document()->getElementById(activeDescendantAttrStr); + Element* target = document()->getElementById(activeDescendantAttrStr); if (!target) return 0; - AccessibilityObject* obj = renderer()->document()->axObjectCache()->getOrCreate(target->renderer()); + AccessibilityObject* obj = axObjectCache()->getOrCreate(target->renderer()); if (obj && obj->isAccessibilityRenderObject()) // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification return obj; @@ -2207,7 +2490,7 @@ void AccessibilityRenderObject::handleActiveDescendantChanged() AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant()); if (activedescendant && shouldFocusActiveDescendant()) - doc->axObjectCache()->postNotification(activedescendant->renderer(), AXObjectCache::AXFocusedUIElementChanged, true); + doc->axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged, true); } AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElement() const @@ -2247,80 +2530,6 @@ AccessibilityObject* AccessibilityRenderObject::observableObject() const return 0; } - -typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; - -struct RoleEntry { - String ariaRole; - AccessibilityRole webcoreRole; -}; - -static const ARIARoleMap& createARIARoleMap() -{ - const RoleEntry roles[] = { - { "application", LandmarkApplicationRole }, - { "article", DocumentArticleRole }, - { "banner", LandmarkBannerRole }, - { "button", ButtonRole }, - { "checkbox", CheckBoxRole }, - { "complementary", LandmarkComplementaryRole }, - { "contentinfo", LandmarkContentInfoRole }, - { "grid", TableRole }, - { "gridcell", CellRole }, - { "columnheader", ColumnHeaderRole }, - { "definition", DefinitionListDefinitionRole }, - { "document", DocumentRole }, - { "rowheader", RowHeaderRole }, - { "group", GroupRole }, - { "heading", HeadingRole }, - { "img", ImageRole }, - { "link", WebCoreLinkRole }, - { "list", ListRole }, - { "listitem", GroupRole }, - { "listbox", ListBoxRole }, - { "log", ApplicationLogRole }, - // "option" isn't here because it may map to different roles depending on the parent element's role - { "main", LandmarkMainRole }, - { "marquee", ApplicationMarqueeRole }, - { "menu", MenuRole }, - { "menubar", GroupRole }, - // "menuitem" isn't here because it may map to different roles depending on the parent element's role - { "menuitemcheckbox", MenuItemRole }, - { "menuitemradio", MenuItemRole }, - { "note", DocumentNoteRole }, - { "navigation", LandmarkNavigationRole }, - { "option", ListBoxOptionRole }, - { "presentation", IgnoredRole }, - { "progressbar", ProgressIndicatorRole }, - { "radio", RadioButtonRole }, - { "radiogroup", RadioGroupRole }, - { "region", DocumentRegionRole }, - { "row", RowRole }, - { "range", SliderRole }, - { "search", LandmarkSearchRole }, - { "separator", SplitterRole }, - { "slider", SliderRole }, - { "spinbutton", ProgressIndicatorRole }, - { "status", ApplicationStatusRole }, - { "textbox", TextAreaRole }, - { "timer", ApplicationTimerRole }, - { "toolbar", ToolbarRole }, - { "tooltip", UserInterfaceTooltipRole } - }; - ARIARoleMap& roleMap = *new ARIARoleMap; - - const unsigned numRoles = sizeof(roles) / sizeof(roles[0]); - for (unsigned i = 0; i < numRoles; ++i) - roleMap.set(roles[i].ariaRole, roles[i].webcoreRole); - return roleMap; -} - -static AccessibilityRole ariaRoleToWebCoreRole(String value) -{ - ASSERT(!value.isEmpty() && !value.isNull()); - static const ARIARoleMap& roleMap = createARIARoleMap(); - return roleMap.get(value); -} AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const { @@ -2332,14 +2541,14 @@ AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const if (role) return role; // selects and listboxes both have options as child roles, but they map to different roles within WebCore - if (equalIgnoringCase(ariaRole,"option")) { + if (equalIgnoringCase(ariaRole, "option")) { if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) return MenuItemRole; if (parentObjectUnignored()->ariaRoleAttribute() == ListBoxRole) return ListBoxOptionRole; } // an aria "menuitem" may map to MenuButton or MenuItem depending on its parent - if (equalIgnoringCase(ariaRole,"menuitem")) { + if (equalIgnoringCase(ariaRole, "menuitem")) { if (parentObjectUnignored()->ariaRoleAttribute() == GroupRole) return MenuButtonRole; if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) @@ -2418,7 +2627,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (m_renderer->isMenuList()) return PopUpButtonRole; - if (headingLevel() != 0) + if (headingLevel()) return HeadingRole; if (node && node->hasTagName(ddTag)) @@ -2436,12 +2645,24 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() return UnknownRole; } +AccessibilityOrientation AccessibilityRenderObject::orientation() const +{ + const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr).string(); + if (equalIgnoringCase(ariaOrientation, "horizontal")) + return AccessibilityOrientationHorizontal; + if (equalIgnoringCase(ariaOrientation, "vertical")) + return AccessibilityOrientationVertical; + + return AccessibilityObject::orientation(); +} + bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const { // Walk the parent chain looking for a parent that has presentational children AccessibilityObject* parent; for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject()) - ; + { } + return parent; } @@ -2474,20 +2695,27 @@ bool AccessibilityRenderObject::canSetFocusAttribute() const return false; switch (roleValue()) { - case WebCoreLinkRole: - case ImageMapLinkRole: - case TextFieldRole: - case TextAreaRole: - case ButtonRole: - case PopUpButtonRole: - case CheckBoxRole: - case RadioButtonRole: - case SliderRole: - return true; - default: - return false; + case WebCoreLinkRole: + case ImageMapLinkRole: + case TextFieldRole: + case TextAreaRole: + case ButtonRole: + case PopUpButtonRole: + case CheckBoxRole: + case RadioButtonRole: + case SliderRole: + return true; + default: + return false; } } + +bool AccessibilityRenderObject::canSetExpandedAttribute() const +{ + // An object can be expanded if it aria-expanded is true or false. + String ariaExpanded = getAttribute(aria_expandedAttr).string(); + return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false"); +} bool AccessibilityRenderObject::canSetValueAttribute() const { @@ -2531,16 +2759,18 @@ bool AccessibilityRenderObject::canHaveChildren() const // Elements that should not have children switch (roleValue()) { - case ImageRole: - case ButtonRole: - case PopUpButtonRole: - case CheckBoxRole: - case RadioButtonRole: - case StaticTextRole: - case ListBoxOptionRole: - return false; - default: - return true; + case ImageRole: + case ButtonRole: + case PopUpButtonRole: + case CheckBoxRole: + case RadioButtonRole: + case TabRole: + case StaticTextRole: + case ListBoxOptionRole: + case ScrollBarRole: + return false; + default: + return true; } } @@ -2604,17 +2834,42 @@ void AccessibilityRenderObject::addChildren() } } +void AccessibilityRenderObject::ariaTreeSelectedRows(AccessibilityChildrenVector& result) +{ + // Get all the rows. + AccessibilityChildrenVector allRows; + ariaTreeRows(allRows); + + // Determine which rows are selected. + bool isMultiselectable = elementAttributeValue(aria_multiselectableAttr); + + // Prefer active descendant over aria-selected. + AccessibilityObject* activeDesc = activeDescendant(); + if (activeDesc && activeDesc->isTreeItem()) { + result.append(activeDesc); + if (!isMultiselectable) + return; + } + + unsigned count = allRows.size(); + for (unsigned k = 0; k < count; ++k) { + if (allRows[k]->isSelected()) { + result.append(allRows[k]); + if (!isMultiselectable) + break; + } + } +} + void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result) { AccessibilityObject* child = firstChild(); - bool isMultiselectable = false; Element* element = static_cast<Element*>(renderer()->node()); if (!element || !element->isElementNode()) // do this check to ensure safety of static_cast above return; - String multiselectablePropertyStr = element->getAttribute("aria-multiselectable").string(); - isMultiselectable = equalIgnoringCase(multiselectablePropertyStr, "true"); + bool isMultiselectable = elementAttributeValue(aria_multiselectableAttr); while (child) { // every child should have aria-role option, and if so, check for selected attribute/state @@ -2625,7 +2880,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) @@ -2642,11 +2897,11 @@ void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& re ASSERT(result.isEmpty()); // only listboxes should be asked for their selected children. - if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes - ASSERT_NOT_REACHED(); - return; - } - return ariaListboxSelectedChildren(result); + AccessibilityRole role = roleValue(); + if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes + ariaListboxSelectedChildren(result); + else if (role == TreeRole) + ariaTreeSelectedRows(result); } void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) @@ -2673,6 +2928,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. @@ -2685,20 +2951,20 @@ const String& AccessibilityRenderObject::actionVerb() const DEFINE_STATIC_LOCAL(const String, noAction, ()); switch (roleValue()) { - case ButtonRole: - return buttonAction; - case TextFieldRole: - case TextAreaRole: - return textFieldAction; - case RadioButtonRole: - return radioButtonAction; - case CheckBoxRole: - return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; - case LinkRole: - case WebCoreLinkRole: - return linkAction; - default: - return noAction; + case ButtonRole: + return buttonAction; + case TextFieldRole: + case TextAreaRole: + return textFieldAction; + case RadioButtonRole: + return radioButtonAction; + case CheckBoxRole: + return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; + case LinkRole: + case WebCoreLinkRole: + return linkAction; + default: + return noAction; } } @@ -2711,4 +2977,101 @@ void AccessibilityRenderObject::updateBackingStore() m_renderer->document()->updateLayoutIgnorePendingStylesheets(); } +static bool isLinkable(const AccessibilityRenderObject& object) +{ + if (!object.renderer()) + return false; + + // See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the elements + // Mozilla considers linkable. + return object.isLink() || object.isImage() || object.renderer()->isText(); +} + +String AccessibilityRenderObject::stringValueForMSAA() const +{ + if (isLinkable(*this)) { + Element* anchor = anchorElement(); + if (anchor && anchor->hasTagName(aTag)) + return static_cast<HTMLAnchorElement*>(anchor)->href(); + } + + return stringValue(); +} + +bool AccessibilityRenderObject::isLinked() const +{ + if (!isLinkable(*this)) + return false; + + Element* anchor = anchorElement(); + if (!anchor || !anchor->hasTagName(aTag)) + return false; + + return !static_cast<HTMLAnchorElement*>(anchor)->href().isEmpty(); +} + +String AccessibilityRenderObject::nameForMSAA() const +{ + if (m_renderer && m_renderer->isText()) + return textUnderElement(); + + return title(); +} + +static bool shouldReturnTagNameAsRoleForMSAA(const Element& element) +{ + // See "document structure", + // https://wiki.mozilla.org/Accessibility/AT-Windows-API + // FIXME: Add the other tag names that should be returned as the role. + return element.hasTagName(h1Tag) || element.hasTagName(h2Tag) + || element.hasTagName(h3Tag) || element.hasTagName(h4Tag) + || element.hasTagName(h5Tag) || element.hasTagName(h6Tag); +} + +String AccessibilityRenderObject::stringRoleForMSAA() const +{ + if (!m_renderer) + return String(); + + Node* node = m_renderer->node(); + if (!node || !node->isElementNode()) + return String(); + + Element* element = static_cast<Element*>(node); + if (!shouldReturnTagNameAsRoleForMSAA(*element)) + return String(); + + return element->tagName(); +} + +String AccessibilityRenderObject::positionalDescriptionForMSAA() const +{ + // See "positional descriptions", + // https://wiki.mozilla.org/Accessibility/AT-Windows-API + if (isHeading()) + return "L" + String::number(headingLevel()); + + // FIXME: Add positional descriptions for other elements. + return String(); +} + +String AccessibilityRenderObject::descriptionForMSAA() const +{ + String description = positionalDescriptionForMSAA(); + if (!description.isEmpty()) + return description; + + description = accessibilityDescription(); + if (!description.isEmpty()) { + // From the Mozilla MSAA implementation: + // "Signal to screen readers that this description is speakable and is not + // a formatted positional information description. Don't localize the + // 'Description: ' part of this string, it will be parsed out by assistive + // technologies." + return "Description: " + description; + } + + return String(); +} + } // namespace WebCore diff --git a/WebCore/accessibility/AccessibilityRenderObject.h b/WebCore/accessibility/AccessibilityRenderObject.h index c6fd748..d537881 100644 --- a/WebCore/accessibility/AccessibilityRenderObject.h +++ b/WebCore/accessibility/AccessibilityRenderObject.h @@ -62,7 +62,7 @@ public: static PassRefPtr<AccessibilityRenderObject> create(RenderObject*); virtual ~AccessibilityRenderObject(); - bool isAccessibilityRenderObject() const { return true; }; + bool isAccessibilityRenderObject() const { return true; } virtual bool isAnchor() const; virtual bool isAttachment() const; @@ -102,12 +102,16 @@ public: virtual bool isReadOnly() const; virtual bool isVisited() const; virtual bool isRequired() const; + virtual bool isLinked() const; + virtual bool isExpanded() const; + virtual void setIsExpanded(bool); const AtomicString& getAttribute(const QualifiedName&) const; virtual bool canSetFocusAttribute() const; virtual bool canSetTextRangeAttributes() const; virtual bool canSetValueAttribute() const; - + virtual bool canSetExpandedAttribute() const; + virtual bool hasIntValue() const; virtual bool accessibilityIsIgnored() const; @@ -119,6 +123,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; @@ -136,6 +141,8 @@ public: virtual AccessibilityObject* correspondingControlForLabelElement() const; virtual AccessibilityObject* correspondingLabelForControlElement() const; + virtual void ariaOwnsElements(AccessibilityChildrenVector&) const; + virtual bool supportsARIAOwns() const; virtual AccessibilityRole ariaRoleAttribute() const; virtual bool isPresentationalChildOfAriaRole() const; virtual bool ariaRoleHasPresentationalChildren() const; @@ -167,7 +174,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; @@ -185,13 +191,17 @@ public: virtual void getDocumentLinks(AccessibilityChildrenVector&); virtual FrameView* documentFrameView() const; virtual String language() const; - + virtual unsigned hierarchicalLevel() const; + virtual const AccessibilityChildrenVector& children(); virtual void setFocused(bool); virtual void setSelectedTextRange(const PlainTextRange&); virtual void setValue(const String&); + virtual void setSelected(bool); + virtual void setSelectedRows(AccessibilityChildrenVector&); virtual void changeValueByPercent(float percentChange); + virtual AccessibilityOrientation orientation() const; virtual void increment(); virtual void decrement(); @@ -201,6 +211,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(); @@ -209,6 +220,14 @@ public: virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const; virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const; virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const; + virtual bool supportsARIAFlowTo() const; + virtual void ariaFlowToElements(AccessibilityChildrenVector&) const; + + virtual bool supportsARIADropping(); + virtual bool supportsARIADragging(); + virtual bool isARIAGrabbed(); + virtual void setARIAGrabbed(bool); + virtual void determineARIADropEffects(Vector<String>&); virtual VisiblePosition visiblePositionForPoint(const IntPoint&) const; virtual VisiblePosition visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const; @@ -224,13 +243,19 @@ public: virtual IntRect doAXBoundsForRange(const PlainTextRange&) const; virtual void updateBackingStore(); - + + virtual String stringValueForMSAA() const; + virtual String stringRoleForMSAA() const; + virtual String nameForMSAA() const; + virtual String descriptionForMSAA() const; + protected: RenderObject* m_renderer; AccessibilityRole m_ariaRole; mutable bool m_childrenDirty; void setRenderObject(RenderObject* renderer) { m_renderer = renderer; } + void ariaLabeledByElements(Vector<Element*>& elements) const; virtual bool isDetached() const { return !m_renderer; } @@ -238,18 +263,30 @@ private: void ariaListboxSelectedChildren(AccessibilityChildrenVector&); void ariaListboxVisibleChildren(AccessibilityChildrenVector&); bool ariaIsHidden() const; + bool isDescendantOfBarrenParent() const; + bool hasTextAlternative() const; + String positionalDescriptionForMSAA() const; Element* menuElementForMenuButton() const; Element* menuItemElementForMenu() const; 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; + void ariaTreeSelectedRows(AccessibilityChildrenVector&); + + bool elementAttributeValue(const QualifiedName&); + void setElementAttributeValue(const QualifiedName&, bool); + + String accessibilityDescriptionForElements(Vector<Element*> &elements) const; + void elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& name) const; + void markChildrenDirty() const { m_childrenDirty = true; } }; diff --git a/WebCore/accessibility/AccessibilitySlider.cpp b/WebCore/accessibility/AccessibilitySlider.cpp index 5aca672..77f4dcc 100644 --- a/WebCore/accessibility/AccessibilitySlider.cpp +++ b/WebCore/accessibility/AccessibilitySlider.cpp @@ -68,18 +68,18 @@ AccessibilityOrientation AccessibilitySlider::orientation() const ControlPart styleAppearance = style->appearance(); switch (styleAppearance) { - case SliderThumbHorizontalPart: - case SliderHorizontalPart: - case MediaSliderPart: - return AccessibilityOrientationHorizontal; + case SliderThumbHorizontalPart: + case SliderHorizontalPart: + case MediaSliderPart: + return AccessibilityOrientationHorizontal; + + case SliderThumbVerticalPart: + case SliderVerticalPart: + case MediaVolumeSliderPart: + return AccessibilityOrientationVertical; - case SliderThumbVerticalPart: - case SliderVerticalPart: - case MediaVolumeSliderPart: - return AccessibilityOrientationVertical; - - default: - return AccessibilityOrientationHorizontal; + default: + return AccessibilityOrientationHorizontal; } } diff --git a/WebCore/accessibility/AccessibilitySlider.h b/WebCore/accessibility/AccessibilitySlider.h index 254ebdd..e1e3812 100644 --- a/WebCore/accessibility/AccessibilitySlider.h +++ b/WebCore/accessibility/AccessibilitySlider.h @@ -33,58 +33,58 @@ namespace WebCore { - class HTMLInputElement; +class HTMLInputElement; - class AccessibilitySlider : public AccessibilityRenderObject { - - public: - static PassRefPtr<AccessibilitySlider> create(RenderObject*); - virtual ~AccessibilitySlider() { } +class AccessibilitySlider : public AccessibilityRenderObject { + +public: + static PassRefPtr<AccessibilitySlider> create(RenderObject*); + virtual ~AccessibilitySlider() { } - virtual AccessibilityRole roleValue() const { return SliderRole; } - virtual bool accessibilityIsIgnored() const { return false; } + virtual AccessibilityRole roleValue() const { return SliderRole; } + virtual bool accessibilityIsIgnored() const { return false; } - virtual bool isSlider() const { return true; } + virtual bool isSlider() const { return true; } - virtual const AccessibilityChildrenVector& children(); - virtual void addChildren(); + virtual const AccessibilityChildrenVector& children(); + virtual void addChildren(); - virtual bool canSetValueAttribute() const { return true; }; - const AtomicString& getAttribute(const QualifiedName& attribute) const; + virtual bool canSetValueAttribute() const { return true; } + const AtomicString& getAttribute(const QualifiedName& attribute) const; - virtual void setValue(const String&); - virtual float valueForRange() const; - virtual float maxValueForRange() const; - virtual float minValueForRange() const; - virtual AccessibilityOrientation orientation() const; + virtual void setValue(const String&); + virtual float valueForRange() const; + virtual float maxValueForRange() const; + virtual float minValueForRange() const; + virtual AccessibilityOrientation orientation() const; - protected: - AccessibilitySlider(RenderObject*); +protected: + AccessibilitySlider(RenderObject*); - private: - HTMLInputElement* element() const; - }; +private: + HTMLInputElement* element() const; +}; - class AccessibilitySliderThumb : public AccessibilityObject { - - public: - static PassRefPtr<AccessibilitySliderThumb> create(); - virtual ~AccessibilitySliderThumb() { } +class AccessibilitySliderThumb : public AccessibilityObject { + +public: + static PassRefPtr<AccessibilitySliderThumb> create(); + virtual ~AccessibilitySliderThumb() { } - virtual AccessibilityRole roleValue() const { return SliderThumbRole; } - virtual bool accessibilityIsIgnored() const { return false; } + virtual AccessibilityRole roleValue() const { return SliderThumbRole; } + virtual bool accessibilityIsIgnored() const { return false; } - void setParentObject(AccessibilitySlider* slider) { m_parentSlider = slider; } - virtual AccessibilityObject* parentObject() const { return m_parentSlider; } + void setParentObject(AccessibilitySlider* slider) { m_parentSlider = slider; } + virtual AccessibilityObject* parentObject() const { return m_parentSlider; } - virtual IntSize size() const; - virtual IntRect elementRect() const; + virtual IntSize size() const; + virtual IntRect elementRect() const; - private: - AccessibilitySliderThumb(); +private: + AccessibilitySliderThumb(); - AccessibilitySlider* m_parentSlider; - }; + AccessibilitySlider* m_parentSlider; +}; } // namespace WebCore diff --git a/WebCore/accessibility/AccessibilityTable.cpp b/WebCore/accessibility/AccessibilityTable.cpp index 928ef2c..9ac1046 100644 --- a/WebCore/accessibility/AccessibilityTable.cpp +++ b/WebCore/accessibility/AccessibilityTable.cpp @@ -29,15 +29,15 @@ #include "config.h" #include "AccessibilityTable.h" +#include "AXObjectCache.h" #include "AccessibilityTableCell.h" #include "AccessibilityTableColumn.h" #include "AccessibilityTableHeaderContainer.h" #include "AccessibilityTableRow.h" -#include "AXObjectCache.h" #include "HTMLNames.h" -#include "HTMLTableElement.h" #include "HTMLTableCaptionElement.h" #include "HTMLTableCellElement.h" +#include "HTMLTableElement.h" #include "RenderObject.h" #include "RenderTable.h" #include "RenderTableCell.h" @@ -149,8 +149,8 @@ bool AccessibilityTable::isTableExposableThroughAccessibility() HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode); // in this case, the developer explicitly assigned a "data" table attribute - if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() || - !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty()) + if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() + || !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty()) return true; RenderStyle* renderStyle = cell->style(); @@ -158,15 +158,15 @@ bool AccessibilityTable::isTableExposableThroughAccessibility() continue; // a cell needs to have matching bordered sides, before it can be considered a bordered cell. - if ((cell->borderTop() > 0 && cell->borderBottom() > 0) || - (cell->borderLeft() > 0 && cell->borderRight() > 0)) + if ((cell->borderTop() > 0 && cell->borderBottom() > 0) + || (cell->borderLeft() > 0 && cell->borderRight() > 0)) borderedCellCount++; // if the cell has a different color from the table and there is cell spacing, // then it is probably a data table cell (spacing and colors take the place of borders) Color cellColor = renderStyle->backgroundColor(); - if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 && - tableBGColor != cellColor && cellColor.alpha() != 1) + if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 + && tableBGColor != cellColor && cellColor.alpha() != 1) backgroundDifferenceCellCount++; // if we've found 10 "good" cells, we don't need to keep searching diff --git a/WebCore/accessibility/AccessibilityTableColumn.cpp b/WebCore/accessibility/AccessibilityTableColumn.cpp index e09d65e..ee8531e 100644 --- a/WebCore/accessibility/AccessibilityTableColumn.cpp +++ b/WebCore/accessibility/AccessibilityTableColumn.cpp @@ -29,12 +29,12 @@ #include "config.h" #include "AccessibilityTableColumn.h" -#include "AccessibilityTableCell.h" #include "AXObjectCache.h" +#include "AccessibilityTableCell.h" #include "HTMLNames.h" #include "RenderTable.h" -#include "RenderTableSection.h" #include "RenderTableCell.h" +#include "RenderTableSection.h" using namespace std; diff --git a/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp b/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp index af9de39..3a2a241 100644 --- a/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp +++ b/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp @@ -29,8 +29,8 @@ #include "config.h" #include "AccessibilityTableHeaderContainer.h" -#include "AccessibilityTable.h" #include "AXObjectCache.h" +#include "AccessibilityTable.h" using namespace std; @@ -79,9 +79,8 @@ void AccessibilityTableHeaderContainer::addChildren() static_cast<AccessibilityTable*>(m_parentTable)->columnHeaders(m_children); unsigned length = m_children.size(); - for (unsigned k = 0; k < length; ++k) { + for (unsigned k = 0; k < length; ++k) m_headerRect.unite(m_children[k]->elementRect()); - } } } // namespace WebCore diff --git a/WebCore/accessibility/AccessibilityTableRow.cpp b/WebCore/accessibility/AccessibilityTableRow.cpp index 53b479e..71f8b2b 100644 --- a/WebCore/accessibility/AccessibilityTableRow.cpp +++ b/WebCore/accessibility/AccessibilityTableRow.cpp @@ -29,8 +29,8 @@ #include "config.h" #include "AccessibilityTableRow.h" -#include "AccessibilityTableCell.h" #include "AXObjectCache.h" +#include "AccessibilityTableCell.h" #include "HTMLNames.h" #include "HTMLTableRowElement.h" #include "RenderObject.h" diff --git a/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h b/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h index d7238e1..85a65ed 100644 --- a/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h +++ b/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h @@ -27,6 +27,8 @@ #ifndef AccessibilityObjectWrapper_h #define AccessibilityObjectWrapper_h +#include <wtf/RefCounted.h> + namespace WebCore { class AccessibilityObject; @@ -41,8 +43,6 @@ namespace WebCore { AccessibilityObjectWrapper(AccessibilityObject* obj) : m_object(obj) { - // FIXME: Remove this once our immediate subclass no longer uses COM. - *addressOfCount() = 0; } AccessibilityObjectWrapper() : m_object(0) { } diff --git a/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp b/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp index cc515ad..c30b006 100644 --- a/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp +++ b/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp @@ -43,6 +43,10 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AX if (!coreObject->isCheckboxOrRadio()) return; g_signal_emit_by_name(coreObject->wrapper(), "state-change", "checked", coreObject->isChecked()); + } else if (notification == AXSelectedChildrenChanged) { + if (!coreObject->isListBox()) + return; + g_signal_emit_by_name(coreObject->wrapper(), "selection-changed"); } } diff --git a/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp b/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp index c5f09ae..16ea948 100644 --- a/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp +++ b/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp @@ -35,6 +35,7 @@ #include "AXObjectCache.h" #include "AccessibilityListBox.h" +#include "AccessibilityListBoxOption.h" #include "AccessibilityRenderObject.h" #include "AccessibilityTable.h" #include "AccessibilityTableCell.h" @@ -43,6 +44,7 @@ #include "AtomicString.h" #include "CString.h" #include "Document.h" +#include "DocumentType.h" #include "Editor.h" #include "Frame.h" #include "FrameView.h" @@ -106,6 +108,11 @@ static AccessibilityObject* core(AtkAction* action) return core(ATK_OBJECT(action)); } +static AccessibilityObject* core(AtkSelection* selection) +{ + return core(ATK_OBJECT(selection)); +} + static AccessibilityObject* core(AtkText* text) { return core(ATK_OBJECT(text)); @@ -131,39 +138,72 @@ static AccessibilityObject* core(AtkTable* table) return core(ATK_OBJECT(table)); } +static AccessibilityObject* core(AtkDocument* document) +{ + return core(ATK_OBJECT(document)); +} + +static const gchar* nameFromChildren(AccessibilityObject* object) +{ + if (!object) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector children = object->children(); + // Currently, object->stringValue() should be an empty String. This might not be the case down the road. + String name = object->stringValue(); + for (unsigned i = 0; i < children.size(); ++i) + name += children.at(i).get()->stringValue(); + return returnString(name); +} + static const gchar* webkit_accessible_get_name(AtkObject* object) { AccessibilityObject* coreObject = core(object); + if (!coreObject->isAccessibilityRenderObject()) + return returnString(coreObject->stringValue()); + + AccessibilityRenderObject* renderObject = static_cast<AccessibilityRenderObject*>(coreObject); if (coreObject->isControl()) { - AccessibilityRenderObject* renderObject = static_cast<AccessibilityRenderObject*>(coreObject); AccessibilityObject* label = renderObject->correspondingLabelForControlElement(); - if (label) { - AccessibilityRenderObject::AccessibilityChildrenVector children = label->children(); - // Currently, label->stringValue() should be an empty String. This - // might not be the case down the road. - String name = label->stringValue(); - for (unsigned i = 0; i < children.size(); ++i) - name += children.at(i).get()->stringValue(); - return returnString(name); + if (label) + return returnString(nameFromChildren(label)); + } + + if (renderObject->isImage() || renderObject->isInputImage()) { + Node* node = renderObject->renderer()->node(); + if (node && node->isHTMLElement()) { + // Get the attribute rather than altText String so as not to fall back on title. + String alt = static_cast<HTMLElement*>(node)->getAttribute(HTMLNames::altAttr); + if (!alt.isEmpty()) + return returnString(alt); } } + return returnString(coreObject->stringValue()); } static const gchar* webkit_accessible_get_description(AtkObject* object) { AccessibilityObject* coreObject = core(object); + Node* node = 0; + if (coreObject->isAccessibilityRenderObject()) + node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); + if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole) + return returnString(coreObject->accessibilityDescription()); // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here. - if (coreObject->roleValue() == TableRole && coreObject->ariaRoleAttribute() == UnknownRole) { - Node* node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); - if (node && node->isHTMLElement()) { - String summary = static_cast<HTMLTableElement*>(node)->summary(); - if (!summary.isEmpty()) - return returnString(summary); - } + if (coreObject->roleValue() == TableRole) { + String summary = static_cast<HTMLTableElement*>(node)->summary(); + if (!summary.isEmpty()) + return returnString(summary); } + // The title attribute should be reliably available as the object's descripton. + // We do not want to fall back on other attributes in its absence. See bug 25524. + String title = static_cast<HTMLElement*>(node)->title(); + if (!title.isEmpty()) + return returnString(title); + return returnString(coreObject->accessibilityDescription()); } @@ -181,9 +221,9 @@ static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, Atk } } -static gpointer webkit_accessible_parent_class = NULL; +static gpointer webkit_accessible_parent_class = 0; -static AtkObject* webkit_accessible_get_parent(AtkObject* object) +static AtkObject* atkParentOfWebView(AtkObject* object) { AccessibilityObject* coreParent = core(object)->parentObjectUnignored(); @@ -203,7 +243,19 @@ static AtkObject* webkit_accessible_get_parent(AtkObject* object) } if (!coreParent) - return NULL; + return 0; + + return coreParent->wrapper(); +} + +static AtkObject* webkit_accessible_get_parent(AtkObject* object) +{ + AccessibilityObject* coreParent = core(object)->parentObjectUnignored(); + if (!coreParent && core(object)->isWebArea()) + return atkParentOfWebView(object); + + if (!coreParent) + return 0; return coreParent->wrapper(); } @@ -216,14 +268,14 @@ static gint webkit_accessible_get_n_children(AtkObject* object) static AtkObject* webkit_accessible_ref_child(AtkObject* object, gint index) { AccessibilityObject* coreObject = core(object); + AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); + if (index < 0 || static_cast<unsigned>(index) >= children.size()) + return 0; - g_return_val_if_fail(index >= 0, NULL); - g_return_val_if_fail(static_cast<size_t>(index) < coreObject->children().size(), NULL); - - AccessibilityObject* coreChild = coreObject->children().at(index).get(); + AccessibilityObject* coreChild = children.at(index).get(); if (!coreChild) - return NULL; + return 0; AtkObject* child = coreChild->wrapper(); atk_object_set_parent(child, object); @@ -237,7 +289,20 @@ static gint webkit_accessible_get_index_in_parent(AtkObject* object) AccessibilityObject* coreObject = core(object); AccessibilityObject* parent = coreObject->parentObjectUnignored(); - g_return_val_if_fail(parent, 0); + if (!parent && core(object)->isWebArea()) { + AtkObject* atkParent = atkParentOfWebView(object); + if (!atkParent) + return -1; + + unsigned count = atk_object_get_n_accessible_children(atkParent); + for (unsigned i = 0; i < count; ++i) { + AtkObject* child = atk_object_ref_accessible_child(atkParent, i); + bool childIsObject = child == object; + g_object_unref(child); + if (childIsObject) + return i; + } + } AccessibilityObject::AccessibilityChildrenVector children = parent->children(); unsigned count = children.size(); @@ -246,7 +311,7 @@ static gint webkit_accessible_get_index_in_parent(AtkObject* object) return i; } - return 0; + return -1; } static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const char* name, const char* value) @@ -261,7 +326,7 @@ static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const c static AtkAttributeSet* webkit_accessible_get_attributes(AtkObject* object) { - AtkAttributeSet* attributeSet = NULL; + AtkAttributeSet* attributeSet = 0; int headingLevel = core(object)->headingLevel(); if (headingLevel) { @@ -361,21 +426,21 @@ static AtkRole atkRole(AccessibilityRole role) static AtkRole webkit_accessible_get_role(AtkObject* object) { - AccessibilityObject* AXObject = core(object); + AccessibilityObject* axObject = core(object); - if (!AXObject) + if (!axObject) return ATK_ROLE_UNKNOWN; // WebCore does not seem to have a role for list items - if (AXObject->isGroup()) { - AccessibilityObject* parent = AXObject->parentObjectUnignored(); + if (axObject->isGroup()) { + AccessibilityObject* parent = axObject->parentObjectUnignored(); if (parent && parent->isList()) return ATK_ROLE_LIST_ITEM; } // WebCore does not know about paragraph role, label role, or section role - if (AXObject->isAccessibilityRenderObject()) { - Node* node = static_cast<AccessibilityRenderObject*>(AXObject)->renderer()->node(); + if (axObject->isAccessibilityRenderObject()) { + Node* node = static_cast<AccessibilityRenderObject*>(axObject)->renderer()->node(); if (node) { if (node->hasTagName(HTMLNames::pTag)) return ATK_ROLE_PARAGRAPH; @@ -387,10 +452,10 @@ static AtkRole webkit_accessible_get_role(AtkObject* object) } // Note: Why doesn't WebCore have a password field for this - if (AXObject->isPasswordField()) + if (axObject->isPasswordField()) return ATK_ROLE_PASSWORD_TEXT; - return atkRole(AXObject->roleValue()); + return atkRole(axObject->roleValue()); } static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet) @@ -549,15 +614,15 @@ webkit_accessible_get_type(void) if (g_once_init_enter(&type_volatile)) { static const GTypeInfo tinfo = { sizeof(WebKitAccessibleClass), - (GBaseInitFunc)NULL, - (GBaseFinalizeFunc)NULL, - (GClassInitFunc)webkit_accessible_class_init, - (GClassFinalizeFunc)NULL, - NULL, /* class data */ + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) webkit_accessible_class_init, + (GClassFinalizeFunc) 0, + 0, /* class data */ sizeof(WebKitAccessible), /* instance size */ 0, /* nb preallocs */ - (GInstanceInitFunc)NULL, - NULL /* value table */ + (GInstanceInitFunc) 0, + 0 /* value table */ }; GType type = g_type_register_static(ATK_TYPE_OBJECT, @@ -581,7 +646,7 @@ static gint webkit_accessible_action_get_n_actions(AtkAction* action) static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i) { - g_return_val_if_fail(i == 0, NULL); + g_return_val_if_fail(i == 0, 0); // TODO: Need a way to provide/localize action descriptions. notImplemented(); return ""; @@ -589,14 +654,14 @@ static const gchar* webkit_accessible_action_get_description(AtkAction* action, static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i) { - g_return_val_if_fail(i == 0, NULL); + g_return_val_if_fail(i == 0, 0); // FIXME: Construct a proper keybinding string. return returnString(core(action)->accessKey().string()); } static const gchar* webkit_accessible_action_get_name(AtkAction* action, gint i) { - g_return_val_if_fail(i == 0, NULL); + g_return_val_if_fail(i == 0, 0); return returnString(core(action)->actionVerb()); } @@ -609,6 +674,145 @@ static void atk_action_interface_init(AtkActionIface* iface) iface->get_name = webkit_accessible_action_get_name; } +// Selection (for controls) + +static AccessibilityObject* optionFromList(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || i < 0) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector options = core(selection)->children(); + if (i < static_cast<gint>(options.size())) + return options.at(i).get(); + + return 0; +} + +static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i) +{ + // i is the ith selection as opposed to the ith child. + + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || i < 0) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + if (coreSelection->isListBox()) + static_cast<AccessibilityListBox*>(coreSelection)->selectedChildren(selectedItems); + + // TODO: Combo boxes + + if (i < static_cast<gint>(selectedItems.size())) + return selectedItems.at(i).get(); + + return 0; +} + +static gboolean webkit_accessible_selection_add_selection(AtkSelection* selection, gint i) +{ + AccessibilityObject* option = optionFromList(selection, i); + if (option && core(selection)->isListBox()) { + AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(option); + listBoxOption->setSelected(true); + return listBoxOption->isSelected(); + } + + return false; +} + +static gboolean webkit_accessible_selection_clear_selection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return false; + + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + if (coreSelection->isListBox()) { + // Set the list of selected items to an empty list; then verify that it worked. + AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); + listBox->setSelectedChildren(selectedItems); + listBox->selectedChildren(selectedItems); + return selectedItems.size() == 0; + } + return false; +} + +static AtkObject* webkit_accessible_selection_ref_selection(AtkSelection* selection, gint i) +{ + AccessibilityObject* option = optionFromSelection(selection, i); + if (option) { + AtkObject* child = option->wrapper(); + g_object_ref(child); + return child; + } + + return 0; +} + +static gint webkit_accessible_selection_get_selection_count(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (coreSelection && coreSelection->isListBox()) { + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + static_cast<AccessibilityListBox*>(coreSelection)->selectedChildren(selectedItems); + return static_cast<gint>(selectedItems.size()); + } + + return 0; +} + +static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i) +{ + AccessibilityObject* option = optionFromList(selection, i); + if (option && core(selection)->isListBox()) + return static_cast<AccessibilityListBoxOption*>(option)->isSelected(); + + return false; +} + +static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i) +{ + // TODO: This is only getting called if i == 0. What is preventing the rest? + AccessibilityObject* option = optionFromSelection(selection, i); + if (option && core(selection)->isListBox()) { + AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(option); + listBoxOption->setSelected(false); + return !listBoxOption->isSelected(); + } + + return false; +} + +static gboolean webkit_accessible_selection_select_all_selection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || !coreSelection->isMultiSelect()) + return false; + + AccessibilityRenderObject::AccessibilityChildrenVector children = coreSelection->children(); + if (coreSelection->isListBox()) { + AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); + listBox->setSelectedChildren(children); + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + listBox->selectedChildren(selectedItems); + return selectedItems.size() == children.size(); + } + + return false; +} + +static void atk_selection_interface_init(AtkSelectionIface* iface) +{ + iface->add_selection = webkit_accessible_selection_add_selection; + iface->clear_selection = webkit_accessible_selection_clear_selection; + iface->ref_selection = webkit_accessible_selection_ref_selection; + iface->get_selection_count = webkit_accessible_selection_get_selection_count; + iface->is_child_selected = webkit_accessible_selection_is_child_selected; + iface->remove_selection = webkit_accessible_selection_remove_selection; + iface->select_all_selection = webkit_accessible_selection_select_all_selection; +} + // Text static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset) @@ -616,6 +820,8 @@ static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, g AccessibilityObject* coreObject = core(text); String ret; unsigned start = startOffset; + if (endOffset == -1) + endOffset = coreObject->stringValue().length(); int length = endOffset - startOffset; if (coreObject->isTextControl()) @@ -656,23 +862,24 @@ static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int fro { CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables); gchar* utf8String = utf8Substr(stringUTF8.data(), from, to); - if (!g_utf8_validate(utf8String, -1, NULL)) { + if (!g_utf8_validate(utf8String, -1, 0)) { g_free(utf8String); return 0; } gsize len = strlen(utf8String); - GString* ret = g_string_new_len(NULL, len); + GString* ret = g_string_new_len(0, len); + gchar* ptr = utf8String; // WebCore introduces line breaks in the text that do not reflect // the layout you see on the screen, replace them with spaces while (len > 0) { gint index, start; - pango_find_paragraph_boundary(utf8String, len, &index, &start); - g_string_append_len(ret, utf8String, index); + pango_find_paragraph_boundary(ptr, len, &index, &start); + g_string_append_len(ret, ptr, index); if (index == start) break; g_string_append_c(ret, ' '); - utf8String += start; + ptr += start; len -= start; } @@ -691,7 +898,7 @@ static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) if (!webView) return 0; - GString* str = g_string_new(NULL); + GString* str = g_string_new(0); AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); if (!accObject) @@ -701,12 +908,30 @@ static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) return 0; // Create a string with the layout as it appears on the screen - InlineTextBox* box = renderText->firstTextBox(); - while (box) { - gchar *text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end()); - g_string_append(str, text); - g_string_append(str, "\n"); - box = box->nextTextBox(); + if (accObject->isTextControl()) { + unsigned textLength = accObject->textLength(); + int lineNumber = 0; + PlainTextRange range = accObject->doAXRangeForLine(lineNumber); + while (range.length) { + // When a line of text wraps in a text area, the final space is removed. + if (range.start + range.length < textLength) + range.length -= 1; + String lineText = accObject->doAXStringForRange(range); + g_string_append(str, lineText.utf8().data()); + g_string_append(str, "\n"); + range = accObject->doAXRangeForLine(++lineNumber); + } + } else { + InlineTextBox* box = renderText->firstTextBox(); + while (box) { + gchar* text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end()); + g_string_append(str, text); + // Newline chars in the source result in separate text boxes, so check + // before adding a newline in the layout. See bug 25415 comment #78. + if (!box->nextOnLineExists()) + g_string_append(str, "\n"); + box = box->nextTextBox(); + } } PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), g_string_free(str, FALSE)); @@ -732,7 +957,7 @@ static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset) { notImplemented(); - return NULL; + return 0; } static gint webkit_accessible_text_get_caret_offset(AtkText* text) @@ -744,13 +969,13 @@ static gint webkit_accessible_text_get_caret_offset(AtkText* text) static AtkAttributeSet* webkit_accessible_text_get_run_attributes(AtkText* text, gint offset, gint* start_offset, gint* end_offset) { notImplemented(); - return NULL; + return 0; } static AtkAttributeSet* webkit_accessible_text_get_default_attributes(AtkText* text) { notImplemented(); - return NULL; + return 0; } static void webkit_accessible_text_get_character_extents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords) @@ -795,7 +1020,7 @@ static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gi return range.start; } -static bool selectionBelongsToObject(AccessibilityObject *coreObject, VisibleSelection& selection) +static bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection) { if (!coreObject->isAccessibilityRenderObject()) return false; @@ -830,7 +1055,7 @@ static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selection // the global one (the API is a bit confusing) if (selection_num != 0 || !selectionBelongsToObject(coreObject, selection)) { *start_offset = *end_offset = 0; - return NULL; + return 0; } *start_offset = selection.start().offsetInContainerNode(); @@ -1016,7 +1241,7 @@ static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkCompone IntPoint pos = atkToContents(core(component), coordType, x, y); AccessibilityObject* target = core(component)->doAccessibilityHitTest(pos); if (!target) - return NULL; + return 0; g_object_ref(target->wrapper()); return target->wrapper(); } @@ -1033,7 +1258,7 @@ static gboolean webkit_accessible_component_grab_focus(AtkComponent* component) return core(component)->isFocused(); } -static void atk_component_interface_init(AtkComponentIface *iface) +static void atk_component_interface_init(AtkComponentIface* iface) { iface->ref_accessible_at_point = webkit_accessible_component_ref_accessible_at_point; iface->get_extents = webkit_accessible_component_get_extents; @@ -1080,32 +1305,68 @@ static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column) return 0; } -static gint cellIndex(AccessibilityTableCell* AXCell, AccessibilityTable* AXTable) +static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable) { // Calculate the cell's index as if we had a traditional Gtk+ table in // which cells are all direct children of the table, arranged row-first. AccessibilityObject::AccessibilityChildrenVector allCells; - AXTable->cells(allCells); + axTable->cells(allCells); AccessibilityObject::AccessibilityChildrenVector::iterator position; - position = std::find(allCells.begin(), allCells.end(), AXCell); + position = std::find(allCells.begin(), allCells.end(), axCell); if (position == allCells.end()) return -1; return position - allCells.begin(); } +static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + AccessibilityObject::AccessibilityChildrenVector allCells; + static_cast<AccessibilityTable*>(accTable)->cells(allCells); + if (0 <= index && static_cast<unsigned>(index) < allCells.size()) { + AccessibilityObject* accCell = allCells.at(index).get(); + return static_cast<AccessibilityTableCell*>(accCell); + } + } + return 0; +} + static AtkObject* webkit_accessible_table_ref_at(AtkTable* table, gint row, gint column) { - AccessibilityTableCell* AXCell = cell(table, row, column); - if (!AXCell) + AccessibilityTableCell* axCell = cell(table, row, column); + if (!axCell) return 0; - return AXCell->wrapper(); + return axCell->wrapper(); } static gint webkit_accessible_table_get_index_at(AtkTable* table, gint row, gint column) { - AccessibilityTableCell* AXCell = cell(table, row, column); - AccessibilityTable* AXTable = static_cast<AccessibilityTable*>(core(table)); - return cellIndex(AXCell, AXTable); + AccessibilityTableCell* axCell = cell(table, row, column); + AccessibilityTable* axTable = static_cast<AccessibilityTable*>(core(table)); + return cellIndex(axCell, axTable); +} + +static gint webkit_accessible_table_get_column_at_index(AtkTable* table, gint index) +{ + AccessibilityTableCell* axCell = cellAtIndex(table, index); + if (axCell){ + pair<int, int> columnRange; + axCell->columnIndexRange(columnRange); + return columnRange.first; + } + return -1; +} + +static gint webkit_accessible_table_get_row_at_index(AtkTable* table, gint index) +{ + AccessibilityTableCell* axCell = cellAtIndex(table, index); + if (axCell){ + pair<int, int> rowRange; + axCell->rowIndexRange(rowRange); + return rowRange.first; + } + return -1; } static gint webkit_accessible_table_get_n_columns(AtkTable* table) @@ -1126,10 +1387,10 @@ static gint webkit_accessible_table_get_n_rows(AtkTable* table) static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint row, gint column) { - AccessibilityTableCell* AXCell = cell(table, row, column); - if (AXCell) { + AccessibilityTableCell* axCell = cell(table, row, column); + if (axCell) { pair<int, int> columnRange; - AXCell->columnIndexRange(columnRange); + axCell->columnIndexRange(columnRange); return columnRange.second; } return 0; @@ -1137,15 +1398,22 @@ static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint r static gint webkit_accessible_table_get_row_extent_at(AtkTable* table, gint row, gint column) { - AccessibilityTableCell* AXCell = cell(table, row, column); - if (AXCell) { + AccessibilityTableCell* axCell = cell(table, row, column); + if (axCell) { pair<int, int> rowRange; - AXCell->rowIndexRange(rowRange); + axCell->rowIndexRange(rowRange); return rowRange.second; } return 0; } +static AtkObject* webkit_accessible_table_get_column_header(AtkTable* table, gint column) +{ + // FIXME: This needs to be implemented. + notImplemented(); + return 0; +} + static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row) { AccessibilityObject* accTable = core(table); @@ -1163,39 +1431,139 @@ static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint r return 0; } +static AtkObject* webkit_accessible_table_get_caption(AtkTable* table) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + Node* node = static_cast<AccessibilityRenderObject*>(accTable)->renderer()->node(); + if (node && node->hasTagName(HTMLNames::tableTag)) { + HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(node)->caption(); + if (caption) + return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->node())->wrapper(); + } + } + return 0; +} + +static const gchar* webkit_accessible_table_get_column_description(AtkTable* table, gint column) +{ + AtkObject* columnHeader = atk_table_get_column_header(table, column); + if (columnHeader) + return returnString(nameFromChildren(core(columnHeader))); + + return 0; +} + +static const gchar* webkit_accessible_table_get_row_description(AtkTable* table, gint row) +{ + AtkObject* rowHeader = atk_table_get_row_header(table, row); + if (rowHeader) + return returnString(nameFromChildren(core(rowHeader))); + + return 0; +} + static void atk_table_interface_init(AtkTableIface* iface) { iface->ref_at = webkit_accessible_table_ref_at; iface->get_index_at = webkit_accessible_table_get_index_at; + iface->get_column_at_index = webkit_accessible_table_get_column_at_index; + iface->get_row_at_index = webkit_accessible_table_get_row_at_index; iface->get_n_columns = webkit_accessible_table_get_n_columns; iface->get_n_rows = webkit_accessible_table_get_n_rows; iface->get_column_extent_at = webkit_accessible_table_get_column_extent_at; iface->get_row_extent_at = webkit_accessible_table_get_row_extent_at; + iface->get_column_header = webkit_accessible_table_get_column_header; iface->get_row_header = webkit_accessible_table_get_row_header; + iface->get_caption = webkit_accessible_table_get_caption; + iface->get_column_description = webkit_accessible_table_get_column_description; + iface->get_row_description = webkit_accessible_table_get_row_description; +} + +static const gchar* documentAttributeValue(AtkDocument* document, const gchar* attribute) +{ + Document* coreDocument = core(document)->document(); + if (!coreDocument) + return 0; + + String value = String(); + if (!g_ascii_strcasecmp(attribute, "DocType") && coreDocument->doctype()) + value = coreDocument->doctype()->name(); + else if (!g_ascii_strcasecmp(attribute, "Encoding")) + value = coreDocument->charset(); + else if (!g_ascii_strcasecmp(attribute, "URI")) + value = coreDocument->documentURI(); + if (!value.isEmpty()) + return returnString(value); + + return 0; +} + +static const gchar* webkit_accessible_document_get_attribute_value(AtkDocument* document, const gchar* attribute) +{ + return documentAttributeValue(document, attribute); +} + +static AtkAttributeSet* webkit_accessible_document_get_attributes(AtkDocument* document) +{ + AtkAttributeSet* attributeSet = 0; + const gchar* attributes [] = {"DocType", "Encoding", "URI"}; + + for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) { + const gchar* value = documentAttributeValue(document, attributes[i]); + if (value) + attributeSet = addAttributeToSet(attributeSet, attributes[i], value); + } + + return attributeSet; +} + +static const gchar* webkit_accessible_document_get_locale(AtkDocument* document) +{ + + // TODO: Should we fall back on lang xml:lang when the following comes up empty? + String language = static_cast<AccessibilityRenderObject*>(core(document))->language(); + if (!language.isEmpty()) + return returnString(language); + + return 0; +} + +static void atk_document_interface_init(AtkDocumentIface* iface) +{ + iface->get_document_attribute_value = webkit_accessible_document_get_attribute_value; + iface->get_document_attributes = webkit_accessible_document_get_attributes; + iface->get_document_locale = webkit_accessible_document_get_locale; } static const GInterfaceInfo AtkInterfacesInitFunctions[] = { {(GInterfaceInitFunc)atk_action_interface_init, - (GInterfaceFinalizeFunc) NULL, NULL}, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_selection_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, {(GInterfaceInitFunc)atk_editable_text_interface_init, - (GInterfaceFinalizeFunc) NULL, NULL}, + (GInterfaceFinalizeFunc) 0, 0}, {(GInterfaceInitFunc)atk_text_interface_init, - (GInterfaceFinalizeFunc) NULL, NULL}, + (GInterfaceFinalizeFunc) 0, 0}, {(GInterfaceInitFunc)atk_component_interface_init, - (GInterfaceFinalizeFunc) NULL, NULL}, + (GInterfaceFinalizeFunc) 0, 0}, {(GInterfaceInitFunc)atk_image_interface_init, - (GInterfaceFinalizeFunc) NULL, NULL}, + (GInterfaceFinalizeFunc) 0, 0}, {(GInterfaceInitFunc)atk_table_interface_init, - (GInterfaceFinalizeFunc) NULL, NULL} + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_document_interface_init, + (GInterfaceFinalizeFunc) 0, 0} }; enum WAIType { WAI_ACTION, + WAI_SELECTION, WAI_EDITABLE_TEXT, WAI_TEXT, WAI_COMPONENT, WAI_IMAGE, - WAI_TABLE + WAI_TABLE, + WAI_DOCUMENT }; static GType GetAtkInterfaceTypeFromWAIType(WAIType type) @@ -1203,6 +1571,8 @@ static GType GetAtkInterfaceTypeFromWAIType(WAIType type) switch (type) { case WAI_ACTION: return ATK_TYPE_ACTION; + case WAI_SELECTION: + return ATK_TYPE_SELECTION; case WAI_EDITABLE_TEXT: return ATK_TYPE_EDITABLE_TEXT; case WAI_TEXT: @@ -1213,6 +1583,8 @@ static GType GetAtkInterfaceTypeFromWAIType(WAIType type) return ATK_TYPE_IMAGE; case WAI_TABLE: return ATK_TYPE_TABLE; + case WAI_DOCUMENT: + return ATK_TYPE_DOCUMENT; } return G_TYPE_INVALID; @@ -1229,6 +1601,10 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) if (!coreObject->actionVerb().isEmpty()) interfaceMask |= 1 << WAI_ACTION; + // Selection + if (coreObject->isListBox()) + interfaceMask |= 1 << WAI_SELECTION; + // Text & Editable Text AccessibilityRole role = coreObject->roleValue(); @@ -1248,6 +1624,10 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) if (role == TableRole) interfaceMask |= 1 << WAI_TABLE; + // Document + if (role == WebAreaRole) + interfaceMask |= 1 << WAI_DOCUMENT; + return interfaceMask; } @@ -1255,10 +1635,10 @@ static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask) { #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */ static char name[WAI_TYPE_NAME_LEN + 1]; - + g_sprintf(name, "WAIType%x", interfaceMask); name[WAI_TYPE_NAME_LEN] = '\0'; - + return name; } @@ -1266,15 +1646,15 @@ static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject) { static const GTypeInfo typeInfo = { sizeof(WebKitAccessibleClass), - (GBaseInitFunc) NULL, - (GBaseFinalizeFunc) NULL, - (GClassInitFunc) NULL, - (GClassFinalizeFunc) NULL, - NULL, /* class data */ + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) 0, + (GClassFinalizeFunc) 0, + 0, /* class data */ sizeof(WebKitAccessible), /* instance size */ 0, /* nb preallocs */ - (GInstanceInitFunc) NULL, - NULL /* value table */ + (GInstanceInitFunc) 0, + 0 /* value table */ }; guint16 interfaceMask = getInterfaceMaskFromObject(coreObject); @@ -1299,7 +1679,7 @@ static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject) WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject) { GType type = getAccessibilityTypeFromObject(coreObject); - AtkObject* object = static_cast<AtkObject*>(g_object_new(type, NULL)); + AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0)); atk_object_initialize(object, coreObject); diff --git a/WebCore/accessibility/mac/AXObjectCacheMac.mm b/WebCore/accessibility/mac/AXObjectCacheMac.mm index 2f18cf3..bf1b22c 100644 --- a/WebCore/accessibility/mac/AXObjectCacheMac.mm +++ b/WebCore/accessibility/mac/AXObjectCacheMac.mm @@ -59,6 +59,13 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotific // Some notifications are unique to Safari and do not have NSAccessibility equivalents. String macNotification; switch (notification) { + case AXActiveDescendantChanged: + // An active descendant change for trees means a selected rows change. + if (obj->isTree()) + macNotification = NSAccessibilitySelectedRowsChangedNotification; + else + macNotification = NSAccessibilityFocusedUIElementChangedNotification; + break; case AXCheckedStateChanged: macNotification = "AXCheckedStateChanged"; break; diff --git a/WebCore/accessibility/mac/AccessibilityObjectMac.mm b/WebCore/accessibility/mac/AccessibilityObjectMac.mm index 217af54..722b03e 100644 --- a/WebCore/accessibility/mac/AccessibilityObjectMac.mm +++ b/WebCore/accessibility/mac/AccessibilityObjectMac.mm @@ -43,6 +43,24 @@ bool AccessibilityObject::accessibilityIgnoreAttachment() const AccessibilityObjectPlatformInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const { + // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline. + AccessibilityObject* axObj = parentObject(); + bool isInTree = false; + while (axObj) { + if (axObj->isTree()) { + isInTree = true; + break; + } + axObj = axObj->parentObjectUnignored(); + } + + // If the object is in a tree, only tree items should be exposed (and the children of tree items). + if (isInTree) { + AccessibilityRole role = roleValue(); + if (role != TreeItemRole && role != StaticTextRole) + return IgnoreObject; + } + return DefaultBehavior; } diff --git a/WebCore/accessibility/mac/AccessibilityObjectWrapper.h b/WebCore/accessibility/mac/AccessibilityObjectWrapper.h index 3b584a9..910305b 100644 --- a/WebCore/accessibility/mac/AccessibilityObjectWrapper.h +++ b/WebCore/accessibility/mac/AccessibilityObjectWrapper.h @@ -29,7 +29,7 @@ #ifndef AccessibilityObjectWrapper_h #define AccessibilityObjectWrapper_h -#import <wtf/RefPtr.h> +#include <wtf/RefPtr.h> #ifdef __OBJC__ @class WebCoreTextMarker; @@ -40,12 +40,11 @@ class WebCoreTextMarkerRange; #endif namespace WebCore { - class AccessibilityObject; - class VisiblePosition; +class AccessibilityObject; +class VisiblePosition; } -@interface AccessibilityObjectWrapper : NSObject -{ +@interface AccessibilityObjectWrapper : NSObject { WebCore::AccessibilityObject* m_object; } diff --git a/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm b/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm index 58e5018..6099b3d 100644 --- a/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm +++ b/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm @@ -119,6 +119,18 @@ using namespace std; #define NSAccessibilityRequiredAttribute @"AXRequired" #endif +#ifndef NSAccessibilityOwnsAttribute +#define NSAccessibilityOwnsAttribute @"AXOwns" +#endif + +#ifndef NSAccessibilityGrabbedAttribute +#define NSAccessibilityGrabbedAttribute @"AXGrabbed" +#endif + +#ifndef NSAccessibilityDropEffectsAttribute +#define NSAccessibilityDropEffectsAttribute @"AXDropEffects" +#endif + #ifdef BUILDING_ON_TIGER typedef unsigned NSUInteger; #define NSAccessibilityValueDescriptionAttribute @"AXValueDescription" @@ -569,6 +581,27 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi return actions; } +- (NSArray*)additionalAccessibilityAttributeNames +{ + if (!m_object) + return nil; + + NSMutableArray *additional = [NSMutableArray array]; + if (m_object->supportsARIAOwns()) + [additional addObject:NSAccessibilityOwnsAttribute]; + + if (m_object->isScrollbar()) + [additional addObject:NSAccessibilityOrientationAttribute]; + + if (m_object->supportsARIADragging()) + [additional addObject:NSAccessibilityGrabbedAttribute]; + + if (m_object->supportsARIADropping()) + [additional addObject:NSAccessibilityDropEffectsAttribute]; + + return additional; +} + - (NSArray*)accessibilityAttributeNames { if (!m_object) @@ -600,6 +633,11 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi static NSArray* groupAttrs = nil; static NSArray* inputImageAttrs = nil; static NSArray* passwordFieldAttrs = nil; + static NSArray* tabListAttrs = nil; + static NSArray* comboBoxAttrs = nil; + static NSArray* outlineAttrs = nil; + static NSArray* outlineRowAttrs = nil; + static NSArray* buttonAttrs = nil; NSMutableArray* tempArray; if (attributes == nil) { attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, @@ -739,6 +777,21 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi controlAttrs = [[NSArray alloc] initWithArray:tempArray]; [tempArray release]; } + if (buttonAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + // Buttons should not expose AXValue. + [tempArray removeObject:NSAccessibilityValueAttribute]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + [tempArray addObject:NSAccessibilityAccessKeyAttribute]; + buttonAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (comboBoxAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:controlAttrs]; + [tempArray addObject:NSAccessibilityExpandedAttribute]; + comboBoxAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } if (tableAttrs == nil) { tempArray = [[NSMutableArray alloc] initWithArray:attributes]; [tempArray addObject:NSAccessibilityRowsAttribute]; @@ -781,9 +834,8 @@ static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(VisiblePositi [tempArray release]; } if (inputImageAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:controlAttrs]; + tempArray = [[NSMutableArray alloc] initWithArray:buttonAttrs]; [tempArray addObject:NSAccessibilityURLAttribute]; - [tempArray addObject:NSAccessibilityAccessKeyAttribute]; inputImageAttrs = [[NSArray alloc] initWithArray:tempArray]; [tempArray release]; } @@ -794,53 +846,96 @@ 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 (outlineAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilitySelectedRowsAttribute]; + [tempArray addObject:NSAccessibilityRowsAttribute]; + [tempArray addObject:NSAccessibilityColumnsAttribute]; + outlineAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (outlineRowAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:tableRowAttrs]; + [tempArray addObject:NSAccessibilityIndexAttribute]; + [tempArray addObject:NSAccessibilityDisclosingAttribute]; + [tempArray addObject:NSAccessibilityDisclosedByRowAttribute]; + [tempArray addObject:NSAccessibilityDisclosureLevelAttribute]; + [tempArray addObject:NSAccessibilityDisclosedRowsAttribute]; + outlineRowAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } - if (m_object->isPasswordField()) - return passwordFieldAttrs; - - if (m_object->isWebArea()) - return webAreaAttrs; + NSArray *objectAttributes = attributes; - if (m_object->isTextControl()) - return textAttrs; - - if (m_object->isAnchor() || m_object->isImage() || m_object->isLink()) - return anchorAttrs; - - if (m_object->isDataTable()) - return tableAttrs; - if (m_object->isTableRow()) - return tableRowAttrs; - if (m_object->isTableColumn()) - return tableColAttrs; - if (m_object->isTableCell()) - return tableCellAttrs; - - if (m_object->isListBox() || m_object->isList()) - return listBoxAttrs; - - if (m_object->isProgressIndicator() || m_object->isSlider()) - return rangeAttrs; - - if (m_object->isInputImage()) - return inputImageAttrs; - - if (m_object->isControl()) - return controlAttrs; - - if (m_object->isGroup()) - return groupAttrs; - - if (m_object->isMenu()) - return menuAttrs; - if (m_object->isMenuBar()) - return menuBarAttrs; - if (m_object->isMenuButton()) - return menuButtonAttrs; - if (m_object->isMenuItem()) - return menuItemAttrs; - - return attributes; + if (m_object->isPasswordField()) + objectAttributes = passwordFieldAttrs; + + else if (m_object->isWebArea()) + objectAttributes = webAreaAttrs; + + else if (m_object->isTextControl()) + objectAttributes = textAttrs; + + else if (m_object->isAnchor() || m_object->isImage() || m_object->isLink()) + objectAttributes = anchorAttrs; + + else if (m_object->isDataTable()) + objectAttributes = tableAttrs; + else if (m_object->isTableRow()) + objectAttributes = tableRowAttrs; + else if (m_object->isTableColumn()) + objectAttributes = tableColAttrs; + else if (m_object->isTableCell()) + objectAttributes = tableCellAttrs; + + else if (m_object->isTree()) + objectAttributes = outlineAttrs; + else if (m_object->isTreeItem()) + objectAttributes = outlineRowAttrs; + + else if (m_object->isListBox() || m_object->isList()) + objectAttributes = listBoxAttrs; + + else if (m_object->isComboBox()) + objectAttributes = comboBoxAttrs; + + else if (m_object->isProgressIndicator() || m_object->isSlider()) + objectAttributes = rangeAttrs; + + // These are processed in order because an input image is a button, and a button is a control. + else if (m_object->isInputImage()) + objectAttributes = inputImageAttrs; + else if (m_object->isButton()) + objectAttributes = buttonAttrs; + else if (m_object->isControl()) + objectAttributes = controlAttrs; + + else if (m_object->isGroup()) + objectAttributes = groupAttrs; + else if (m_object->isTabList()) + objectAttributes = tabListAttrs; + + else if (m_object->isMenu()) + objectAttributes = menuAttrs; + else if (m_object->isMenuBar()) + objectAttributes = menuBarAttrs; + else if (m_object->isMenuButton()) + objectAttributes = menuButtonAttrs; + else if (m_object->isMenuItem()) + objectAttributes = menuItemAttrs; + + NSArray *additionalAttributes = [self additionalAccessibilityAttributeNames]; + if ([additionalAttributes count]) + objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes]; + + return objectAttributes; } - (VisiblePositionRange)visiblePositionRangeForTextMarkerRange:(WebCoreTextMarkerRange*) textMarkerRange @@ -936,6 +1031,7 @@ static const AccessibilityRoleMap& createAccessibilityRoleMap() { GroupRole, NSAccessibilityGroupRole }, { RadioGroupRole, NSAccessibilityRadioGroupRole }, { ListRole, NSAccessibilityListRole }, + { DirectoryRole, NSAccessibilityListRole }, { ScrollBarRole, NSAccessibilityScrollBarRole }, { ValueIndicatorRole, NSAccessibilityValueIndicatorRole }, { ImageRole, NSAccessibilityImageRole }, @@ -992,17 +1088,24 @@ static const AccessibilityRoleMap& createAccessibilityRoleMap() { LandmarkMainRole, NSAccessibilityGroupRole }, { LandmarkNavigationRole, NSAccessibilityGroupRole }, { LandmarkSearchRole, NSAccessibilityGroupRole }, + { ApplicationAlertRole, NSAccessibilityGroupRole }, + { ApplicationAlertDialogRole, NSAccessibilityGroupRole }, + { ApplicationDialogRole, NSAccessibilityGroupRole }, { ApplicationLogRole, NSAccessibilityGroupRole }, { ApplicationMarqueeRole, NSAccessibilityGroupRole }, { ApplicationStatusRole, NSAccessibilityGroupRole }, { ApplicationTimerRole, NSAccessibilityGroupRole }, { DocumentRole, NSAccessibilityGroupRole }, { DocumentArticleRole, NSAccessibilityGroupRole }, + { DocumentMathRole, NSAccessibilityGroupRole }, { DocumentNoteRole, NSAccessibilityGroupRole }, { DocumentRegionRole, NSAccessibilityGroupRole }, { UserInterfaceTooltipRole, NSAccessibilityGroupRole }, - - + { TabRole, NSAccessibilityRadioButtonRole }, + { TabListRole, NSAccessibilityTabGroupRole }, + { TabPanelRole, NSAccessibilityGroupRole }, + { TreeRole, NSAccessibilityOutlineRole }, + { TreeItemRole, NSAccessibilityRowRole }, }; AccessibilityRoleMap& roleMap = *new AccessibilityRoleMap; @@ -1041,6 +1144,9 @@ static NSString* roleValueToNSString(AccessibilityRole value) } } + if (m_object->isTreeItem()) + return NSAccessibilityOutlineRowSubrole; + if (m_object->isList()) { AccessibilityList* listObject = static_cast<AccessibilityList*>(m_object); if (listObject->isUnorderedList() || listObject->isOrderedList()) @@ -1065,6 +1171,12 @@ static NSString* roleValueToNSString(AccessibilityRole value) return @"AXLandmarkNavigation"; case LandmarkSearchRole: return @"AXLandmarkSearch"; + case ApplicationAlertRole: + return @"AXApplicationAlert"; + case ApplicationAlertDialogRole: + return @"AXApplicationAlertDialog"; + case ApplicationDialogRole: + return @"AXApplicationDialog"; case ApplicationLogRole: return @"AXApplicationLog"; case ApplicationMarqueeRole: @@ -1077,14 +1189,23 @@ static NSString* roleValueToNSString(AccessibilityRole value) return @"AXDocument"; case DocumentArticleRole: return @"AXDocumentArticle"; + case DocumentMathRole: + return @"AXDocumentMath"; case DocumentNoteRole: return @"AXDocumentNote"; case DocumentRegionRole: return @"AXDocumentRegion"; case UserInterfaceTooltipRole: return @"AXUserInterfaceTooltip"; + case TabPanelRole: + return @"AXTabPanel"; + case DefinitionListTermRole: + return @"AXTerm"; + case DefinitionListDefinitionRole: + return @"AXDefinition"; + // Default doesn't return anything, so roles defined below can be chosen. default: - return nil; + break; } if (m_object->isMediaTimeline()) @@ -1122,6 +1243,12 @@ static NSString* roleValueToNSString(AccessibilityRole value) return AXARIAContentGroupText(@"ARIALandmarkNavigation"); case LandmarkSearchRole: return AXARIAContentGroupText(@"ARIALandmarkSearch"); + case ApplicationAlertRole: + return AXARIAContentGroupText(@"ARIAApplicationAlert"); + case ApplicationAlertDialogRole: + return AXARIAContentGroupText(@"ARIAApplicationAlertDialog"); + case ApplicationDialogRole: + return AXARIAContentGroupText(@"ARIAApplicationDialog"); case ApplicationLogRole: return AXARIAContentGroupText(@"ARIAApplicationLog"); case ApplicationMarqueeRole: @@ -1134,12 +1261,20 @@ static NSString* roleValueToNSString(AccessibilityRole value) return AXARIAContentGroupText(@"ARIADocument"); case DocumentArticleRole: return AXARIAContentGroupText(@"ARIADocumentArticle"); + case DocumentMathRole: + return AXARIAContentGroupText(@"ARIADocumentMath"); case DocumentNoteRole: return AXARIAContentGroupText(@"ARIADocumentNote"); case DocumentRegionRole: return AXARIAContentGroupText(@"ARIADocumentRegion"); case UserInterfaceTooltipRole: return AXARIAContentGroupText(@"ARIAUserInterfaceTooltip"); + case TabPanelRole: + return AXARIAContentGroupText(@"ARIATabPanel"); + case DefinitionListTermRole: + return AXDefinitionListTermText(); + case DefinitionListDefinitionRole: + return AXDefinitionListDefinitionText(); } } @@ -1158,6 +1293,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]); @@ -1195,6 +1334,16 @@ static NSString* roleValueToNSString(AccessibilityRole value) return fv->platformWidget(); } + // Tree item (changed to AXRows) can only report the tree (AXOutline) as its parent. + if (m_object->isTreeItem()) { + AccessibilityObject* parent = m_object->parentObjectUnignored(); + while (parent) { + if (parent->isTree()) + return parent->wrapper(); + parent = parent->parentObjectUnignored(); + } + } + return m_object->parentObjectUnignored()->wrapper(); } @@ -1204,6 +1353,19 @@ static NSString* roleValueToNSString(AccessibilityRole value) if (children != nil) return children; } + + // The tree's (AXOutline) children are supposed to be its rows and columns. + // The ARIA spec doesn't have columns, so we just need rows. + if (m_object->isTree()) + return [self accessibilityAttributeValue:NSAccessibilityRowsAttribute]; + + // A tree item should only expose its content as its children (not its rows) + if (m_object->isTreeItem()) { + AccessibilityObject::AccessibilityChildrenVector contentCopy; + m_object->ariaTreeItemContent(contentCopy); + return convertToNSArray(contentCopy); + } + return convertToNSArray(m_object->children()); } @@ -1305,7 +1467,7 @@ static NSString* roleValueToNSString(AccessibilityRole value) if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityValueAttribute]) return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityValueAttribute]; } - if (m_object->isProgressIndicator() || m_object->isSlider()) + if (m_object->isProgressIndicator() || m_object->isSlider() || m_object->isScrollbar()) return [NSNumber numberWithFloat:m_object->valueForRange()]; if (m_object->hasIntValue()) return [NSNumber numberWithInt:m_object->intValue()]; @@ -1318,6 +1480,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 +1531,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] || @@ -1438,6 +1635,69 @@ static NSString* roleValueToNSString(AccessibilityRole value) } } + if (m_object->isTree()) { + if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy; + m_object->selectedChildren(selectedChildrenCopy); + return convertToNSArray(selectedChildrenCopy); + } + if ([attributeName isEqualToString:NSAccessibilityRowsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector rowsCopy; + m_object->ariaTreeRows(rowsCopy); + return convertToNSArray(rowsCopy); + } + + // TreeRoles do not support columns, but Mac AX expects to be able to ask about columns at the least. + if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute]) + return [NSArray array]; + } + + if (m_object->isTreeItem()) { + if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) { + AccessibilityObject* parent = m_object->parentObject(); + for (; parent && !parent->isTree(); parent = parent->parentObject()) + { } + + if (!parent) + return nil; + + // Find the index of this item by iterating the parents. + AccessibilityObject::AccessibilityChildrenVector rowsCopy; + parent->ariaTreeRows(rowsCopy); + size_t count = rowsCopy.size(); + for (size_t k = 0; k < count; ++k) + if (rowsCopy[k]->wrapper() == self) + return [NSNumber numberWithUnsignedInt:k]; + + return nil; + } + + // The rows that are considered inside this row. + if ([attributeName isEqualToString:NSAccessibilityDisclosedRowsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector rowsCopy; + m_object->ariaTreeItemDisclosedRows(rowsCopy); + return convertToNSArray(rowsCopy); + } + + // The row that contains this row. It should be the same as the first parent that is a treeitem. + if ([attributeName isEqualToString:NSAccessibilityDisclosedByRowAttribute]) { + AccessibilityObject* parent = m_object->parentObject(); + while (parent) { + if (parent->isTreeItem()) + return parent->wrapper(); + // If the parent is the tree itself, then this value == nil. + if (parent->isTree()) + return nil; + parent = parent->parentObject(); + } + return nil; + } + if ([attributeName isEqualToString:NSAccessibilityDisclosureLevelAttribute]) + return [NSNumber numberWithInt:m_object->hierarchicalLevel()]; + if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]) + return [NSNumber numberWithBool:m_object->isExpanded()]; + } + if ((m_object->isListBox() || m_object->isList()) && [attributeName isEqualToString:NSAccessibilityOrientationAttribute]) return NSAccessibilityVerticalOrientationValue; @@ -1504,9 +1764,32 @@ static NSString* roleValueToNSString(AccessibilityRole value) if ([attributeName isEqualToString:NSAccessibilityLanguageAttribute]) return m_object->language(); + if ([attributeName isEqualToString:NSAccessibilityExpandedAttribute]) + return [NSNumber numberWithBool:m_object->isExpanded()]; + if ([attributeName isEqualToString:NSAccessibilityRequiredAttribute]) return [NSNumber numberWithBool:m_object->isRequired()]; + if ([attributeName isEqualToString:NSAccessibilityOwnsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector ariaOwns; + m_object->ariaOwnsElements(ariaOwns); + return convertToNSArray(ariaOwns); + } + + if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) + return [NSNumber numberWithBool:m_object->isARIAGrabbed()]; + + if ([attributeName isEqualToString:NSAccessibilityDropEffectsAttribute]) { + Vector<String> dropEffects; + m_object->determineARIADropEffects(dropEffects); + size_t length = dropEffects.size(); + + NSMutableArray* dropEffectsArray = [NSMutableArray arrayWithCapacity:length]; + for (size_t i = 0; i < length; ++i) + [dropEffectsArray addObject:dropEffects[i]]; + return dropEffectsArray; + } + // this is used only by DumpRenderTree for testing if ([attributeName isEqualToString:@"AXClickPoint"]) return [NSValue valueWithPoint:m_object->clickPoint()]; @@ -1570,11 +1853,20 @@ static NSString* roleValueToNSString(AccessibilityRole value) if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) return m_object->canSetSelectedChildrenAttribute(); + if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]) + return m_object->canSetExpandedAttribute(); + + if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) + return YES; + if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute] || [attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute] || [attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) return m_object->canSetTextRangeAttributes(); + if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) + return YES; + return NO; } @@ -1736,9 +2028,13 @@ static NSString* roleValueToNSString(AccessibilityRole value) - (void)accessibilityPerformShowMenuAction { - // This needs to be performed in an iteration of the run loop that did not start from an AX call. - // If it's the same run loop iteration, the menu open notification won't be sent - [self performSelector:@selector(accessibilityShowContextMenu) withObject:nil afterDelay:0.0]; + if (m_object->roleValue() == ComboBoxRole) + m_object->setIsExpanded(true); + else { + // This needs to be performed in an iteration of the run loop that did not start from an AX call. + // If it's the same run loop iteration, the menu open notification won't be sent + [self performSelector:@selector(accessibilityShowContextMenu) withObject:nil afterDelay:0.0]; + } } - (void)accessibilityShowContextMenu @@ -1852,7 +2148,15 @@ static NSString* roleValueToNSString(AccessibilityRole value) } else if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) { m_object->makeRangeVisible(PlainTextRange(range.location, range.length)); } - } + } else if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]) + m_object->setIsExpanded([number boolValue]); + else if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector selectedRows; + convertToVector(array, selectedRows); + if (m_object->isTree()) + m_object->setSelectedRows(selectedRows); + } else if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) + m_object->setARIAGrabbed([number boolValue]); } static RenderObject* rendererForView(NSView* view) @@ -2186,7 +2490,12 @@ static RenderObject* rendererForView(NSView* view) m_object->updateBackingStore(); if (!m_object) return NSNotFound; - + + // Tree objects return their rows as their children. We can use the original method + // here, because we won't gain any speed up. + if (m_object->isTree()) + return [super accessibilityIndexOfChild:child]; + const AccessibilityObject::AccessibilityChildrenVector& children = m_object->children(); if (children.isEmpty()) @@ -2212,6 +2521,11 @@ static RenderObject* rendererForView(NSView* view) return 0; if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + // Tree items object returns a different set of children than those that are in children() + // because an AXOutline (the mac role is becomes) has some odd stipulations. + if (m_object->isTree() || m_object->isTreeItem()) + return [[self accessibilityAttributeValue:NSAccessibilityChildrenAttribute] count]; + const AccessibilityObject::AccessibilityChildrenVector& children = m_object->children(); if (children.isEmpty()) return [[self renderWidgetChildren] count]; @@ -2243,6 +2557,9 @@ static RenderObject* rendererForView(NSView* view) NSUInteger arrayLength = min(childCount - index, maxCount); return [children subarrayWithRange:NSMakeRange(index, arrayLength)]; + } else if (m_object->isTree()) { + // Tree objects return their rows as their children. We can use the original method in this case. + return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; } const AccessibilityObject::AccessibilityChildrenVector& children = m_object->children(); diff --git a/WebCore/accessibility/win/AXObjectCacheWin.cpp b/WebCore/accessibility/win/AXObjectCacheWin.cpp index a1bdcc0..863793c 100644 --- a/WebCore/accessibility/win/AXObjectCacheWin.cpp +++ b/WebCore/accessibility/win/AXObjectCacheWin.cpp @@ -74,6 +74,7 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotific DWORD msaaEvent; switch (notification) { case AXFocusedUIElementChanged: + case AXActiveDescendantChanged: msaaEvent = EVENT_OBJECT_FOCUS; break; |
