diff options
Diffstat (limited to 'Source/WebCore/accessibility')
66 files changed, 18484 insertions, 0 deletions
diff --git a/Source/WebCore/accessibility/AXObjectCache.cpp b/Source/WebCore/accessibility/AXObjectCache.cpp new file mode 100644 index 0000000..d0d19f7 --- /dev/null +++ b/Source/WebCore/accessibility/AXObjectCache.cpp @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AXObjectCache.h" + +#include "AccessibilityARIAGrid.h" +#include "AccessibilityARIAGridCell.h" +#include "AccessibilityARIAGridRow.h" +#include "AccessibilityImageMapLink.h" +#include "AccessibilityList.h" +#include "AccessibilityListBox.h" +#include "AccessibilityListBoxOption.h" +#include "AccessibilityMediaControls.h" +#include "AccessibilityMenuList.h" +#include "AccessibilityMenuListOption.h" +#include "AccessibilityMenuListPopup.h" +#include "AccessibilityProgressIndicator.h" +#include "AccessibilityRenderObject.h" +#include "AccessibilityScrollView.h" +#include "AccessibilityScrollbar.h" +#include "AccessibilitySlider.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableCell.h" +#include "AccessibilityTableColumn.h" +#include "AccessibilityTableHeaderContainer.h" +#include "AccessibilityTableRow.h" +#include "Document.h" +#include "FocusController.h" +#include "Frame.h" +#include "HTMLAreaElement.h" +#include "HTMLImageElement.h" +#include "HTMLNames.h" +#if ENABLE(VIDEO) +#include "MediaControlElements.h" +#endif +#include "InputElement.h" +#include "Page.h" +#include "RenderListBox.h" +#include "RenderMenuList.h" +#include "RenderProgress.h" +#include "RenderSlider.h" +#include "RenderTable.h" +#include "RenderTableCell.h" +#include "RenderTableRow.h" +#include "RenderView.h" +#include "ScrollView.h" + +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +using namespace HTMLNames; + +bool AXObjectCache::gAccessibilityEnabled = false; +bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; + +AXObjectCache::AXObjectCache(const Document* doc) + : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) +{ + m_document = const_cast<Document*>(doc); +} + +AXObjectCache::~AXObjectCache() +{ + HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end(); + for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) { + AccessibilityObject* obj = (*it).second.get(); + detachWrapper(obj); + obj->detach(); + removeAXID(obj); + } +} + +AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) +{ + // Find the corresponding accessibility object for the HTMLAreaElement. This should be + // in the list of children for its corresponding image. + if (!areaElement) + return 0; + + HTMLImageElement* imageElement = areaElement->imageElement(); + if (!imageElement) + return 0; + + AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer()); + if (!axRenderImage) + return 0; + + AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children(); + unsigned count = imageChildren.size(); + for (unsigned k = 0; k < count; ++k) { + AccessibilityObject* child = imageChildren[k].get(); + if (!child->isImageMapLink()) + continue; + + if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement) + return child; + } + + return 0; +} + +AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) +{ + // get the focused node in the page + Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); + Node* focusedNode = focusedDocument->focusedNode(); + if (!focusedNode) + focusedNode = focusedDocument; + + if (focusedNode->hasTagName(areaTag)) + return focusedImageMapUIElement(static_cast<HTMLAreaElement*>(focusedNode)); + + RenderObject* focusedNodeRenderer = focusedNode->renderer(); + if (!focusedNodeRenderer) + return 0; + + AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer); + + if (obj->shouldFocusActiveDescendant()) { + if (AccessibilityObject* descendant = obj->activeDescendant()) + obj = descendant; + } + + // the HTML element, for example, is focusable but has an AX object that is ignored + if (obj->accessibilityIsIgnored()) + obj = obj->parentObjectUnignored(); + + return obj; +} + +AccessibilityObject* AXObjectCache::get(Widget* widget) +{ + if (!widget) + return 0; + + AXID axID = m_widgetObjectMapping.get(widget); + ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); + if (!axID) + return 0; + + return m_objects.get(axID).get(); +} + +AccessibilityObject* AXObjectCache::get(RenderObject* renderer) +{ + if (!renderer) + return 0; + + AXID axID = m_renderObjectMapping.get(renderer); + ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); + if (!axID) + return 0; + + return m_objects.get(axID).get(); +} + +// FIXME: This probably belongs on Node. +// FIXME: This should take a const char*, but one caller passes nullAtom. +bool nodeHasRole(Node* node, const String& role) +{ + if (!node || !node->isElementNode()) + return false; + + return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role); +} + +static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer) +{ + // FIXME: How could renderer->node() ever not be an Element? + Node* node = renderer->node(); + + // 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). + if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) + || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag))))) + return AccessibilityList::create(renderer); + + // aria tables + if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) + return AccessibilityARIAGrid::create(renderer); + if (nodeHasRole(node, "row")) + return AccessibilityARIAGridRow::create(renderer); + if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) + return AccessibilityARIAGridCell::create(renderer); + +#if ENABLE(VIDEO) + // media controls + if (node && node->isMediaControlElement()) + return AccessibilityMediaControl::create(renderer); +#endif + + if (renderer->isBoxModelObject()) { + RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); + if (cssBox->isListBox()) + return AccessibilityListBox::create(toRenderListBox(cssBox)); + if (cssBox->isMenuList()) + return AccessibilityMenuList::create(toRenderMenuList(cssBox)); + + // standard tables + if (cssBox->isTable()) + return AccessibilityTable::create(toRenderTable(cssBox)); + if (cssBox->isTableRow()) + return AccessibilityTableRow::create(toRenderTableRow(cssBox)); + if (cssBox->isTableCell()) + return AccessibilityTableCell::create(toRenderTableCell(cssBox)); + +#if ENABLE(PROGRESS_TAG) + // progress bar + if (cssBox->isProgress()) + return AccessibilityProgressIndicator::create(toRenderProgress(cssBox)); +#endif + + // input type=range + if (cssBox->isSlider()) + return AccessibilitySlider::create(toRenderSlider(cssBox)); + } + + return AccessibilityRenderObject::create(renderer); +} + +AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) +{ + if (!widget) + return 0; + + if (AccessibilityObject* obj = get(widget)) + return obj; + + RefPtr<AccessibilityObject> newObj = 0; + if (widget->isFrameView()) + newObj = AccessibilityScrollView::create(static_cast<ScrollView*>(widget)); + else if (widget->isScrollbar()) + newObj = AccessibilityScrollbar::create(static_cast<Scrollbar*>(widget)); + + getAXID(newObj.get()); + + m_widgetObjectMapping.set(widget, newObj->axObjectID()); + m_objects.set(newObj->axObjectID(), newObj); + attachWrapper(newObj.get()); + return newObj.get(); +} + +AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) +{ + if (!renderer) + return 0; + + if (AccessibilityObject* obj = get(renderer)) + return obj; + + RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer); + + getAXID(newObj.get()); + + m_renderObjectMapping.set(renderer, newObj->axObjectID()); + m_objects.set(newObj->axObjectID(), newObj); + attachWrapper(newObj.get()); + return newObj.get(); +} + +AccessibilityObject* AXObjectCache::rootObject() +{ + return getOrCreate(m_document->view()); +} + +AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) +{ + RefPtr<AccessibilityObject> obj = 0; + + // 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; + case MenuListPopupRole: + obj = AccessibilityMenuListPopup::create(); + break; + case MenuListOptionRole: + obj = AccessibilityMenuListOption::create(); + break; + default: + obj = 0; + } + + if (obj) + getAXID(obj.get()); + else + return 0; + + m_objects.set(obj->axObjectID(), obj); + attachWrapper(obj.get()); + return obj.get(); +} + +void AXObjectCache::remove(AXID axID) +{ + if (!axID) + return; + + // first fetch object to operate some cleanup functions on it + AccessibilityObject* obj = m_objects.get(axID).get(); + if (!obj) + return; + + detachWrapper(obj); + obj->detach(); + removeAXID(obj); + + // finally remove the object + if (!m_objects.take(axID)) + return; + + ASSERT(m_objects.size() >= m_idsInUse.size()); +} + +void AXObjectCache::remove(RenderObject* renderer) +{ + if (!renderer) + return; + + AXID axID = m_renderObjectMapping.get(renderer); + remove(axID); + m_renderObjectMapping.remove(renderer); +} + +void AXObjectCache::remove(Widget* view) +{ + if (!view) + return; + + AXID axID = m_widgetObjectMapping.get(view); + remove(axID); + m_widgetObjectMapping.remove(view); +} + + +#if !PLATFORM(WIN) || OS(WINCE) +AXID AXObjectCache::platformGenerateAXID() const +{ + static AXID lastUsedID = 0; + + // Generate a new ID. + AXID objID = lastUsedID; + do { + ++objID; + } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); + + lastUsedID = objID; + + return objID; +} +#endif + +AXID AXObjectCache::getAXID(AccessibilityObject* obj) +{ + // check for already-assigned ID + AXID objID = obj->axObjectID(); + if (objID) { + ASSERT(m_idsInUse.contains(objID)); + return objID; + } + + objID = platformGenerateAXID(); + + m_idsInUse.add(objID); + obj->setAXObjectID(objID); + + return objID; +} + +void AXObjectCache::removeAXID(AccessibilityObject* object) +{ + if (!object) + return; + + AXID objID = object->axObjectID(); + if (!objID) + return; + ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); + ASSERT(m_idsInUse.contains(objID)); + object->setAXObjectID(0); + m_idsInUse.remove(objID); +} + +#if HAVE(ACCESSIBILITY) +void AXObjectCache::contentChanged(RenderObject* renderer) +{ + AccessibilityObject* object = getOrCreate(renderer); + if (object) + object->contentChanged(); +} +#endif + +void AXObjectCache::childrenChanged(RenderObject* renderer) +{ + if (!renderer) + return; + + AXID axID = m_renderObjectMapping.get(renderer); + if (!axID) + return; + + AccessibilityObject* obj = m_objects.get(axID).get(); + if (obj) + obj->childrenChanged(); +} + +void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*) +{ + m_notificationPostTimer.stop(); + + unsigned i = 0, count = m_notificationsToPost.size(); + for (i = 0; i < count; ++i) { + AccessibilityObject* obj = m_notificationsToPost[i].first.get(); +#ifndef NDEBUG + // Make sure none of the render views are in the process of being layed out. + // Notifications should only be sent after the renderer has finished + if (obj->isAccessibilityRenderObject()) { + AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj); + RenderObject* renderer = renderObj->renderer(); + if (renderer && renderer->view()) + ASSERT(!renderer->view()->layoutState()); + } +#endif + + postPlatformNotification(obj, m_notificationsToPost[i].second); + } + + m_notificationsToPost.clear(); +} + +#if HAVE(ACCESSIBILITY) +void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType) +{ + // Notifications for text input objects are sent to that object. + // All others are sent to the top WebArea. + if (!renderer) + return; + + // Get an accessibility object that already exists. One should not be created here + // because a render update may be in progress and creating an AX object can re-trigger a layout + RefPtr<AccessibilityObject> object = get(renderer); + while (!object && renderer) { + renderer = renderer->parent(); + object = get(renderer); + } + + if (!renderer) + return; + + postNotification(object.get(), renderer->document(), notification, postToElement, postType); +} + +void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType) +{ + if (object && !postToElement) + object = object->observableObject(); + + if (!object && document) + object = get(document->renderer()); + + if (!object) + return; + + if (postType == PostAsynchronously) { + m_notificationsToPost.append(make_pair(object, notification)); + if (!m_notificationPostTimer.isActive()) + m_notificationPostTimer.startOneShot(0); + } else + postPlatformNotification(object, notification); +} + +void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) +{ + // postToElement is false so that you can pass in any child of an element and it will go up the parent tree + // to find the container which should send out the notification. + postNotification(renderer, AXSelectedChildrenChanged, false); +} + +void AXObjectCache::nodeTextChangeNotification(RenderObject* renderer, AXTextChange textChange, unsigned offset, unsigned count) +{ + if (!renderer) + return; + + // Delegate on the right platform + AccessibilityObject* obj = getOrCreate(renderer); + nodeTextChangePlatformNotification(obj, textChange, offset, count); +} +#endif + +#if HAVE(ACCESSIBILITY) + +void AXObjectCache::handleScrollbarUpdate(ScrollView* view) +{ + if (!view) + return; + + // We don't want to create a scroll view from this method, only update an existing one. + AccessibilityObject* scrollViewObject = get(view); + if (scrollViewObject) + scrollViewObject->updateChildrenIfNecessary(); +} + +void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer) +{ + if (!renderer) + return; + AccessibilityObject* obj = getOrCreate(renderer); + if (obj) + obj->handleAriaExpandedChanged(); +} + +void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer) +{ + if (!renderer) + return; + AccessibilityObject* obj = getOrCreate(renderer); + if (obj) + obj->handleActiveDescendantChanged(); +} + +void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer) +{ + if (!renderer) + return; + AccessibilityObject* obj = getOrCreate(renderer); + if (obj && obj->isAccessibilityRenderObject()) + static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole(); +} +#endif + +VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData) +{ + if (!isNodeInUse(textMarkerData.node)) + return VisiblePosition(); + + VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity); + Position deepPos = visiblePos.deepEquivalent(); + if (deepPos.isNull()) + return VisiblePosition(); + + RenderObject* renderer = deepPos.node()->renderer(); + if (!renderer) + return VisiblePosition(); + + AXObjectCache* cache = renderer->document()->axObjectCache(); + if (!cache->isIDinUse(textMarkerData.axID)) + return VisiblePosition(); + + if (deepPos.node() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) + return VisiblePosition(); + + return visiblePos; +} + +void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) +{ + // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. + // This also allows callers to check for failure by looking at textMarkerData upon return. + memset(&textMarkerData, 0, sizeof(TextMarkerData)); + + if (visiblePos.isNull()) + return; + + Position deepPos = visiblePos.deepEquivalent(); + Node* domNode = deepPos.node(); + ASSERT(domNode); + if (!domNode) + return; + + if (domNode->isHTMLElement()) { + InputElement* inputElement = toInputElement(static_cast<Element*>(domNode)); + if (inputElement && inputElement->isPasswordField()) + return; + } + + // locate the renderer, which must exist for a visible dom node + RenderObject* renderer = domNode->renderer(); + ASSERT(renderer); + + // find or create an accessibility object for this renderer + AXObjectCache* cache = renderer->document()->axObjectCache(); + RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer); + + textMarkerData.axID = obj.get()->axObjectID(); + textMarkerData.node = domNode; + textMarkerData.offset = deepPos.deprecatedEditingOffset(); + textMarkerData.affinity = visiblePos.affinity(); + + cache->setNodeInUse(domNode); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AXObjectCache.h b/Source/WebCore/accessibility/AXObjectCache.h new file mode 100644 index 0000000..670d6e0 --- /dev/null +++ b/Source/WebCore/accessibility/AXObjectCache.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AXObjectCache_h +#define AXObjectCache_h + +#include "AccessibilityObject.h" +#include "Timer.h" +#include <limits.h> +#include <wtf/Forward.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/RefPtr.h> + +#ifdef __OBJC__ +@class WebCoreTextMarker; +#else +class WebCoreTextMarker; +#endif + +namespace WebCore { + +class Document; +class HTMLAreaElement; +class Node; +class Page; +class RenderObject; +class ScrollView; +class VisiblePosition; +class Widget; + +struct TextMarkerData { + AXID axID; + Node* node; + int offset; + EAffinity affinity; +}; + +enum PostType { PostSynchronously, PostAsynchronously }; + +class AXObjectCache : public Noncopyable { +public: + AXObjectCache(const Document*); + ~AXObjectCache(); + + static AccessibilityObject* focusedUIElementForPage(const Page*); + + AccessibilityObject* rootObject(); + + // For AX objects with elements that back them. + AccessibilityObject* getOrCreate(RenderObject*); + AccessibilityObject* getOrCreate(Widget*); + + // 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(Widget*); + void remove(AXID); + + void detachWrapper(AccessibilityObject*); + void attachWrapper(AccessibilityObject*); + void childrenChanged(RenderObject*); + void selectedChildrenChanged(RenderObject*); + // Called by a node when text or a text equivalent (e.g. alt) attribute is changed. + void contentChanged(RenderObject*); + + void handleActiveDescendantChanged(RenderObject*); + void handleAriaRoleChanged(RenderObject*); + void handleFocusedUIElementChanged(RenderObject* oldFocusedRenderer, RenderObject* newFocusedRenderer); + void handleScrolledToAnchor(const Node* anchorNode); + void handleAriaExpandedChange(RenderObject*); + void handleScrollbarUpdate(ScrollView*); + + 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(); } + + // This is a weak reference cache for knowing if Nodes used by TextMarkers are valid. + void setNodeInUse(Node* n) { m_textMarkerNodes.add(n); } + void removeNodeForUse(Node* n) { m_textMarkerNodes.remove(n); } + bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); } + + // Text marker utilities. + void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&); + VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&); + + enum AXNotification { + AXActiveDescendantChanged, + AXCheckedStateChanged, + AXChildrenChanged, + AXFocusedUIElementChanged, + AXLayoutComplete, + AXLoadComplete, + AXSelectedChildrenChanged, + AXSelectedTextChanged, + AXValueChanged, + AXScrolledToAnchor, + AXLiveRegionChanged, + AXMenuListValueChanged, + AXRowCountChanged, + AXRowCollapsed, + AXRowExpanded, + AXInvalidStatusChanged, + }; + + void postNotification(RenderObject*, AXNotification, bool postToElement, PostType = PostAsynchronously); + void postNotification(AccessibilityObject*, Document*, AXNotification, bool postToElement, PostType = PostAsynchronously); + + enum AXTextChange { + AXTextInserted, + AXTextDeleted, + }; + + void nodeTextChangeNotification(RenderObject*, AXTextChange, unsigned offset, unsigned count); + + bool nodeHasRole(Node*, const AtomicString& role); + +protected: + void postPlatformNotification(AccessibilityObject*, AXNotification); + void nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned offset, unsigned count); + +private: + Document* m_document; + HashMap<AXID, RefPtr<AccessibilityObject> > m_objects; + HashMap<RenderObject*, AXID> m_renderObjectMapping; + HashMap<Widget*, AXID> m_widgetObjectMapping; + HashSet<Node*> m_textMarkerNodes; + 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>*); + + static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement*); + + AXID getAXID(AccessibilityObject*); + AccessibilityObject* get(Widget*); +}; + +bool nodeHasRole(Node*, const 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, PostType) { } +inline void AXObjectCache::postNotification(AccessibilityObject*, Document*, AXNotification, bool postToElement, PostType) { } +inline void AXObjectCache::postPlatformNotification(AccessibilityObject*, AXNotification) { } +inline void AXObjectCache::nodeTextChangeNotification(RenderObject*, AXTextChange, unsigned, unsigned) { } +inline void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, unsigned) { } +inline void AXObjectCache::handleFocusedUIElementChanged(RenderObject*, RenderObject*) { } +inline void AXObjectCache::handleScrolledToAnchor(const Node*) { } +inline void AXObjectCache::contentChanged(RenderObject*) { } +inline void AXObjectCache::handleAriaExpandedChange(RenderObject*) { } +inline void AXObjectCache::handleScrollbarUpdate(ScrollView*) { } +#endif + +} + +#endif diff --git a/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp b/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp new file mode 100644 index 0000000..8651a80 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityARIAGrid.h" + +#include "AXObjectCache.h" +#include "AccessibilityTableCell.h" +#include "AccessibilityTableColumn.h" +#include "AccessibilityTableHeaderContainer.h" +#include "AccessibilityTableRow.h" +#include "RenderObject.h" + +using namespace std; + +namespace WebCore { + +AccessibilityARIAGrid::AccessibilityARIAGrid(RenderObject* renderer) + : AccessibilityTable(renderer) +{ +#if ACCESSIBILITY_TABLES + m_isAccessibilityTable = true; +#else + m_isAccessibilityTable = false; +#endif +} + +AccessibilityARIAGrid::~AccessibilityARIAGrid() +{ +} + +PassRefPtr<AccessibilityARIAGrid> AccessibilityARIAGrid::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityARIAGrid(renderer)); +} + +void AccessibilityARIAGrid::addChild(AccessibilityObject* child, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount) +{ + if (!child || !child->isTableRow() || child->ariaRoleAttribute() != RowRole) + return; + + AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(child); + if (appendedRows.contains(row)) + return; + + // store the maximum number of columns + unsigned rowCellCount = row->children().size(); + if (rowCellCount > columnCount) + columnCount = rowCellCount; + + row->setRowIndex((int)m_rows.size()); + m_rows.append(row); + m_children.append(row); + appendedRows.add(row); +} + +void AccessibilityARIAGrid::addChildren() +{ + ASSERT(!m_haveChildren); + + if (!isAccessibilityTable()) { + AccessibilityRenderObject::addChildren(); + return; + } + + m_haveChildren = true; + if (!m_renderer) + return; + + AXObjectCache* axCache = m_renderer->document()->axObjectCache(); + + // add only rows that are labeled as aria rows + HashSet<AccessibilityObject*> appendedRows; + unsigned columnCount = 0; + for (RefPtr<AccessibilityObject> child = firstChild(); child; child = child->nextSibling()) { + + // in case the render tree doesn't match the expected ARIA hierarchy, look at the children + if (child->accessibilityIsIgnored()) { + if (!child->hasChildren()) + child->addChildren(); + + AccessibilityChildrenVector children = child->children(); + unsigned length = children.size(); + for (unsigned i = 0; i < length; ++i) + addChild(children[i].get(), appendedRows, columnCount); + } else + addChild(child.get(), appendedRows, columnCount); + } + + // make the columns based on the number of columns in the first body + for (unsigned i = 0; i < columnCount; ++i) { + AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole)); + column->setColumnIndex((int)i); + column->setParentTable(this); + m_columns.append(column); + if (!column->accessibilityIsIgnored()) + m_children.append(column); + } + + AccessibilityObject* headerContainerObject = headerContainer(); + if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored()) + m_children.append(headerContainerObject); +} + +AccessibilityTableCell* AccessibilityARIAGrid::cellForColumnAndRow(unsigned column, unsigned row) +{ + if (!m_renderer) + return 0; + + updateChildrenIfNecessary(); + + if (column >= columnCount() || row >= rowCount()) + return 0; + + AccessibilityObject* tableRow = m_rows[row].get(); + if (!tableRow) + return 0; + + AccessibilityChildrenVector children = tableRow->children(); + // in case this row had fewer columns than other rows + AccessibilityObject* tableCell = 0; + if (column >= children.size()) + return 0; + + tableCell = children[column].get(); + return static_cast<AccessibilityTableCell*>(tableCell); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGrid.h b/Source/WebCore/accessibility/AccessibilityARIAGrid.h new file mode 100644 index 0000000..02ff4d0 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityARIAGrid.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityARIAGrid_h +#define AccessibilityARIAGrid_h + +#include "AccessibilityTable.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class AccessibilityTableCell; +class AccessibilityTableHeaderContainer; + +class AccessibilityARIAGrid : public AccessibilityTable { + +private: + AccessibilityARIAGrid(RenderObject*); +public: + static PassRefPtr<AccessibilityARIAGrid> create(RenderObject*); + virtual ~AccessibilityARIAGrid(); + + virtual bool isAriaTable() const { return true; } + + virtual void addChildren(); + + virtual AccessibilityTableCell* cellForColumnAndRow(unsigned column, unsigned row); + +private: + // ARIA treegrids and grids support selected rows. + virtual bool supportsSelectedRows() { return true; } + + void addChild(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); +}; + +} // namespace WebCore + +#endif // AccessibilityARIAGrid_h diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp b/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp new file mode 100644 index 0000000..c8157f5 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityARIAGridCell.h" + +#include "AccessibilityObject.h" +#include "AccessibilityTableRow.h" + +using namespace std; + +namespace WebCore { + +AccessibilityARIAGridCell::AccessibilityARIAGridCell(RenderObject* renderer) + : AccessibilityTableCell(renderer) +{ +} + +AccessibilityARIAGridCell::~AccessibilityARIAGridCell() +{ +} + +PassRefPtr<AccessibilityARIAGridCell> AccessibilityARIAGridCell::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityARIAGridCell(renderer)); +} + +AccessibilityObject* AccessibilityARIAGridCell::parentTable() const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isTableRow()) + return 0; + + parent = parent->parentObjectUnignored(); + if (!parent || !parent->isAccessibilityTable()) + return 0; + + return parent; +} + +void AccessibilityARIAGridCell::rowIndexRange(pair<int, int>& rowRange) +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isTableRow()) + return; + + // as far as I can tell, grid cells cannot span rows + rowRange.first = static_cast<AccessibilityTableRow*>(parent)->rowIndex(); + rowRange.second = 1; +} + +void AccessibilityARIAGridCell::columnIndexRange(pair<int, int>& columnRange) +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isTableRow()) + return; + + AccessibilityChildrenVector siblings = parent->children(); + unsigned childrenSize = siblings.size(); + for (unsigned k = 0; k < childrenSize; ++k) { + if (siblings[k].get() == this) { + columnRange.first = k; + break; + } + } + + // as far as I can tell, grid cells cannot span columns + columnRange.second = 1; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridCell.h b/Source/WebCore/accessibility/AccessibilityARIAGridCell.h new file mode 100644 index 0000000..2923de8 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityARIAGridCell.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityARIAGridCell_h +#define AccessibilityARIAGridCell_h + +#include "AccessibilityTableCell.h" + +namespace WebCore { + +class AccessibilityARIAGridCell : public AccessibilityTableCell { + +private: + AccessibilityARIAGridCell(RenderObject*); +public: + static PassRefPtr<AccessibilityARIAGridCell> create(RenderObject*); + virtual ~AccessibilityARIAGridCell(); + + // fills in the start location and row span of cell + virtual void rowIndexRange(pair<int, int>& rowRange); + // fills in the start location and column span of cell + virtual void columnIndexRange(pair<int, int>& columnRange); + +protected: + virtual AccessibilityObject* parentTable() const; +}; + +} // namespace WebCore + +#endif // AccessibilityARIAGridCell_h diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp b/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp new file mode 100644 index 0000000..2b96036 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityARIAGridRow.h" + +#include "AccessibilityObject.h" +#include "AccessibilityTable.h" +#include "RenderObject.h" + +using namespace std; + +namespace WebCore { + +AccessibilityARIAGridRow::AccessibilityARIAGridRow(RenderObject* renderer) + : AccessibilityTableRow(renderer) +{ +} + +AccessibilityARIAGridRow::~AccessibilityARIAGridRow() +{ +} + +PassRefPtr<AccessibilityARIAGridRow> AccessibilityARIAGridRow::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityARIAGridRow(renderer)); +} + +bool AccessibilityARIAGridRow::isARIATreeGridRow() const +{ + AccessibilityObject* parent = parentTable(); + if (!parent) + return false; + + return parent->ariaRoleAttribute() == TreeGridRole; +} + +void AccessibilityARIAGridRow::disclosedRows(AccessibilityChildrenVector& disclosedRows) +{ + // The contiguous disclosed rows will be the rows in the table that + // have an aria-level of plus 1 from this row. + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isAccessibilityTable()) + return; + + // Search for rows that match the correct level. + // Only take the subsequent rows from this one that are +1 from this row's level. + int index = rowIndex(); + if (index < 0) + return; + + unsigned level = hierarchicalLevel(); + AccessibilityChildrenVector& allRows = static_cast<AccessibilityTable*>(parent)->rows(); + int rowCount = allRows.size(); + for (int k = index + 1; k < rowCount; ++k) { + AccessibilityObject* row = allRows[k].get(); + // Stop at the first row that doesn't match the correct level. + if (row->hierarchicalLevel() != level + 1) + break; + + disclosedRows.append(row); + } +} + +AccessibilityObject* AccessibilityARIAGridRow::disclosedByRow() const +{ + // The row that discloses this one is the row in the table + // that is aria-level subtract 1 from this row. + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isAccessibilityTable()) + return 0; + + // If the level is 1 or less, than nothing discloses this row. + unsigned level = hierarchicalLevel(); + if (level <= 1) + return 0; + + // Search for the previous row that matches the correct level. + int index = rowIndex(); + AccessibilityChildrenVector& allRows = static_cast<AccessibilityTable*>(parent)->rows(); + int rowCount = allRows.size(); + if (index >= rowCount) + return 0; + + for (int k = index - 1; k >= 0; --k) { + AccessibilityObject* row = allRows[k].get(); + if (row->hierarchicalLevel() == level - 1) + return row; + } + + return 0; +} + +AccessibilityObject* AccessibilityARIAGridRow::parentTable() const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent->isAccessibilityTable()) + return 0; + + return parent; +} + +AccessibilityObject* AccessibilityARIAGridRow::headerObject() +{ + AccessibilityChildrenVector rowChildren = children(); + unsigned childrenCount = rowChildren.size(); + for (unsigned i = 0; i < childrenCount; ++i) { + AccessibilityObject* cell = rowChildren[i].get(); + if (cell->ariaRoleAttribute() == RowHeaderRole) + return cell; + } + + return 0; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridRow.h b/Source/WebCore/accessibility/AccessibilityARIAGridRow.h new file mode 100644 index 0000000..f89ea92 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityARIAGridRow.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityARIAGridRow_h +#define AccessibilityARIAGridRow_h + +#include "AccessibilityTableRow.h" + +namespace WebCore { + +class AccessibilityARIAGridRow : public AccessibilityTableRow { + +private: + AccessibilityARIAGridRow(RenderObject*); +public: + static PassRefPtr<AccessibilityARIAGridRow> create(RenderObject*); + virtual ~AccessibilityARIAGridRow(); + + void disclosedRows(AccessibilityChildrenVector&); + AccessibilityObject* disclosedByRow() const; + + virtual AccessibilityObject* headerObject(); + virtual AccessibilityObject* parentTable() const; + +private: + virtual bool isARIATreeGridRow() const; +}; + +} // namespace WebCore + +#endif // AccessibilityARIAGridRow_h diff --git a/Source/WebCore/accessibility/AccessibilityAllInOne.cpp b/Source/WebCore/accessibility/AccessibilityAllInOne.cpp new file mode 100644 index 0000000..1c2836e --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityAllInOne.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// 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> +#include <AccessibilityImageMapLink.cpp> +#include <AccessibilityList.cpp> +#include <AccessibilityListBox.cpp> +#include <AccessibilityListBoxOption.cpp> +#include <AccessibilityMediaControls.cpp> +#include <AccessibilityObject.cpp> +#include <AccessibilityRenderObject.cpp> +#include <AccessibilityScrollView.cpp> +#include <AccessibilityScrollbar.cpp> +#include <AccessibilitySlider.cpp> +#include <AccessibilityTable.cpp> +#include <AccessibilityTableCell.cpp> +#include <AccessibilityTableColumn.cpp> +#include <AccessibilityTableHeaderContainer.cpp> +#include <AccessibilityTableRow.cpp> diff --git a/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp b/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp new file mode 100644 index 0000000..5676206 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityImageMapLink.h" + +#include "AXObjectCache.h" +#include "AccessibilityRenderObject.h" +#include "Document.h" +#include "HTMLNames.h" +#include "RenderBoxModelObject.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityImageMapLink::AccessibilityImageMapLink() + : m_areaElement(0) + , m_mapElement(0) +{ +} + +AccessibilityImageMapLink::~AccessibilityImageMapLink() +{ +} + +PassRefPtr<AccessibilityImageMapLink> AccessibilityImageMapLink::create() +{ + return adoptRef(new AccessibilityImageMapLink()); +} + +AccessibilityObject* AccessibilityImageMapLink::parentObject() const +{ + if (m_parent) + return m_parent; + + if (!m_mapElement.get() || !m_mapElement->renderer()) + return 0; + + return m_mapElement->document()->axObjectCache()->getOrCreate(m_mapElement->renderer()); +} + +AccessibilityRole AccessibilityImageMapLink::roleValue() const +{ + if (!m_areaElement) + return WebCoreLinkRole; + + const AtomicString& ariaRole = getAttribute(roleAttr); + if (!ariaRole.isEmpty()) + return AccessibilityObject::ariaRoleToWebCoreRole(ariaRole); + + return WebCoreLinkRole; +} + +Element* AccessibilityImageMapLink::actionElement() const +{ + return anchorElement(); +} + +Element* AccessibilityImageMapLink::anchorElement() const +{ + return m_areaElement.get(); +} + +KURL AccessibilityImageMapLink::url() const +{ + if (!m_areaElement.get()) + return KURL(); + + return m_areaElement->href(); +} + +String AccessibilityImageMapLink::accessibilityDescription() const +{ + const AtomicString& ariaLabel = getAttribute(aria_labelAttr); + if (!ariaLabel.isEmpty()) + return ariaLabel; + const AtomicString& alt = getAttribute(altAttr); + if (!alt.isEmpty()) + return alt; + + return String(); +} + +String AccessibilityImageMapLink::title() const +{ + const AtomicString& title = getAttribute(titleAttr); + if (!title.isEmpty()) + return title; + const AtomicString& summary = getAttribute(summaryAttr); + if (!summary.isEmpty()) + return summary; + + return String(); +} + +IntRect AccessibilityImageMapLink::elementRect() const +{ + if (!m_mapElement.get() || !m_areaElement.get()) + return IntRect(); + + RenderObject* renderer; + if (m_parent && m_parent->isAccessibilityRenderObject()) + renderer = static_cast<AccessibilityRenderObject*>(m_parent)->renderer(); + else + renderer = m_mapElement->renderer(); + + if (!renderer) + return IntRect(); + + return m_areaElement->getRect(renderer); +} + +String AccessibilityImageMapLink::stringValueForMSAA() const +{ + return url(); +} + +String AccessibilityImageMapLink::nameForMSAA() const +{ + return accessibilityDescription(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityImageMapLink.h b/Source/WebCore/accessibility/AccessibilityImageMapLink.h new file mode 100644 index 0000000..4b02b2b --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityImageMapLink.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityImageMapLink_h +#define AccessibilityImageMapLink_h + +#include "AccessibilityObject.h" +#include "HTMLAreaElement.h" +#include "HTMLMapElement.h" + +namespace WebCore { + +class AccessibilityImageMapLink : public AccessibilityObject { + +private: + AccessibilityImageMapLink(); +public: + static PassRefPtr<AccessibilityImageMapLink> create(); + virtual ~AccessibilityImageMapLink(); + + void setHTMLAreaElement(HTMLAreaElement* element) { m_areaElement = element; } + HTMLAreaElement* areaElement() const { return m_areaElement.get(); } + + void setHTMLMapElement(HTMLMapElement* element) { m_mapElement = element; } + HTMLMapElement* mapElement() const { return m_mapElement.get(); } + + virtual Node* node() const { return m_areaElement.get(); } + void setParent(AccessibilityObject* parent) { m_parent = parent; } + + virtual AccessibilityRole roleValue() const; + virtual bool accessibilityIsIgnored() const { return false; } + virtual bool isEnabled() const { return true; } + + virtual AccessibilityObject* parentObject() const; + virtual Element* anchorElement() const; + 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 IntRect elementRect() const; + +private: + RefPtr<HTMLAreaElement> m_areaElement; + RefPtr<HTMLMapElement> m_mapElement; + AccessibilityObject* m_parent; + + virtual bool isImageMapLink() const { return true; } +}; + +} // namespace WebCore + +#endif // AccessibilityImageMapLink_h diff --git a/Source/WebCore/accessibility/AccessibilityList.cpp b/Source/WebCore/accessibility/AccessibilityList.cpp new file mode 100644 index 0000000..073b0fc --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityList.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityList.h" + +#include "AXObjectCache.h" +#include "HTMLNames.h" +#include "RenderObject.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityList::AccessibilityList(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityList::~AccessibilityList() +{ +} + +PassRefPtr<AccessibilityList> AccessibilityList::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityList(renderer)); +} + +bool AccessibilityList::accessibilityIsIgnored() const +{ + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; + + // lists don't appear on tiger/leopard on the mac +#if ACCESSIBILITY_LISTS + return false; +#else + return true; +#endif +} + +bool AccessibilityList::isUnorderedList() const +{ + if (!m_renderer) + return false; + + Node* node = m_renderer->node(); + + // The ARIA spec says the "list" role is supposed to mimic a UL or OL tag. + // Since it can't be both, it's probably OK to say that it's an un-ordered list. + // On the Mac, there's no distinction to the client. + if (ariaRoleAttribute() == ListRole) + return true; + + return node && node->hasTagName(ulTag); +} + +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); +} + +bool AccessibilityList::isDefinitionList() const +{ + if (!m_renderer) + return false; + + Node* node = m_renderer->node(); + return node && node->hasTagName(dlTag); +} + + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityList.h b/Source/WebCore/accessibility/AccessibilityList.h new file mode 100644 index 0000000..b7265b2 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityList.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityList_h +#define AccessibilityList_h + +#if PLATFORM(MAC) && (defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)) +#define ACCESSIBILITY_LISTS 0 +#else +#define ACCESSIBILITY_LISTS 1 +#endif + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityList : public AccessibilityRenderObject { + +private: + AccessibilityList(RenderObject*); +public: + static PassRefPtr<AccessibilityList> create(RenderObject*); + virtual ~AccessibilityList(); + + virtual bool isList() const { return true; } + bool isUnorderedList() const; + bool isOrderedList() const; + bool isDefinitionList() const; + + virtual AccessibilityRole roleValue() const { return ListRole; } + virtual bool accessibilityIsIgnored() const; + +}; + +} // namespace WebCore + +#endif // AccessibilityList_h diff --git a/Source/WebCore/accessibility/AccessibilityListBox.cpp b/Source/WebCore/accessibility/AccessibilityListBox.cpp new file mode 100644 index 0000000..191a041 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityListBox.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityListBox.h" + +#include "AXObjectCache.h" +#include "AccessibilityListBoxOption.h" +#include "HTMLNames.h" +#include "HTMLSelectElement.h" +#include "HitTestResult.h" +#include "RenderListBox.h" +#include "RenderObject.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityListBox::AccessibilityListBox(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityListBox::~AccessibilityListBox() +{ +} + +PassRefPtr<AccessibilityListBox> AccessibilityListBox::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityListBox(renderer)); +} + +bool AccessibilityListBox::canSetSelectedChildrenAttribute() const +{ + Node* selectNode = m_renderer->node(); + if (!selectNode) + return false; + + return !static_cast<HTMLSelectElement*>(selectNode)->disabled(); +} + +void AccessibilityListBox::addChildren() +{ + Node* selectNode = m_renderer->node(); + if (!selectNode) + return; + + m_haveChildren = true; + + const Vector<Element*>& listItems = static_cast<HTMLSelectElement*>(selectNode)->listItems(); + unsigned length = listItems.size(); + for (unsigned i = 0; i < length; i++) { + // The cast to HTMLElement below is safe because the only other possible listItem type + // would be a WMLElement, but WML builds don't use accessibility features at all. + AccessibilityObject* listOption = listBoxOptionAccessibilityObject(static_cast<HTMLElement*>(listItems[i])); + if (listOption && !listOption->accessibilityIsIgnored()) + m_children.append(listOption); + } +} + +void AccessibilityListBox::setSelectedChildren(AccessibilityChildrenVector& children) +{ + if (!canSetSelectedChildrenAttribute()) + return; + + Node* selectNode = m_renderer->node(); + if (!selectNode) + return; + + // disable any selected options + unsigned length = m_children.size(); + for (unsigned i = 0; i < length; i++) { + AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(m_children[i].get()); + if (listBoxOption->isSelected()) + listBoxOption->setSelected(false); + } + + length = children.size(); + for (unsigned i = 0; i < length; i++) { + AccessibilityObject* obj = children[i].get(); + if (obj->roleValue() != ListBoxOptionRole) + continue; + + static_cast<AccessibilityListBoxOption*>(obj)->setSelected(true); + } +} + +void AccessibilityListBox::selectedChildren(AccessibilityChildrenVector& result) +{ + ASSERT(result.isEmpty()); + + if (!hasChildren()) + addChildren(); + + unsigned length = m_children.size(); + for (unsigned i = 0; i < length; i++) { + if (static_cast<AccessibilityListBoxOption*>(m_children[i].get())->isSelected()) + result.append(m_children[i]); + } +} + +void AccessibilityListBox::visibleChildren(AccessibilityChildrenVector& result) +{ + ASSERT(result.isEmpty()); + + if (!hasChildren()) + addChildren(); + + unsigned length = m_children.size(); + for (unsigned i = 0; i < length; i++) { + if (toRenderListBox(m_renderer)->listIndexIsVisible(i)) + result.append(m_children[i]); + } +} + +AccessibilityObject* AccessibilityListBox::listBoxOptionAccessibilityObject(HTMLElement* element) const +{ + // skip hr elements + if (!element || element->hasTagName(hrTag)) + return 0; + + AccessibilityObject* listBoxObject = m_renderer->document()->axObjectCache()->getOrCreate(ListBoxOptionRole); + static_cast<AccessibilityListBoxOption*>(listBoxObject)->setHTMLElement(element); + + return listBoxObject; +} + +bool AccessibilityListBox::accessibilityIsIgnored() const +{ + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; + + return false; +} + +AccessibilityObject* AccessibilityListBox::elementAccessibilityHitTest(const IntPoint& point) const +{ + // the internal HTMLSelectElement methods for returning a listbox option at a point + // ignore optgroup elements. + if (!m_renderer) + return 0; + + Node* node = m_renderer->node(); + if (!node) + return 0; + + IntRect parentRect = boundingBoxRect(); + + AccessibilityObject* listBoxOption = 0; + unsigned length = m_children.size(); + for (unsigned i = 0; i < length; i++) { + IntRect rect = toRenderListBox(m_renderer)->itemBoundingBoxRect(parentRect.x(), parentRect.y(), i); + // The cast to HTMLElement below is safe because the only other possible listItem type + // would be a WMLElement, but WML builds don't use accessibility features at all. + if (rect.contains(point)) { + listBoxOption = m_children[i].get(); + break; + } + } + + if (listBoxOption && !listBoxOption->accessibilityIsIgnored()) + return listBoxOption; + + return axObjectCache()->getOrCreate(m_renderer); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityListBox.h b/Source/WebCore/accessibility/AccessibilityListBox.h new file mode 100644 index 0000000..cfccb52 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityListBox.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityListBox_h +#define AccessibilityListBox_h + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityListBox : public AccessibilityRenderObject { + +private: + AccessibilityListBox(RenderObject*); +public: + static PassRefPtr<AccessibilityListBox> create(RenderObject*); + virtual ~AccessibilityListBox(); + + virtual bool isListBox() const { return true; } + + virtual bool canSetFocusAttribute() const { return true; } + virtual bool canSetSelectedChildrenAttribute() const; + void setSelectedChildren(AccessibilityChildrenVector&); + virtual AccessibilityRole roleValue() const { return ListBoxRole; } + + virtual void selectedChildren(AccessibilityChildrenVector&); + virtual void visibleChildren(AccessibilityChildrenVector&); + + virtual void addChildren(); + +private: + AccessibilityObject* listBoxOptionAccessibilityObject(HTMLElement*) const; + virtual bool accessibilityIsIgnored() const; + virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; +}; + +} // namespace WebCore + +#endif // AccessibilityListBox_h diff --git a/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp b/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp new file mode 100644 index 0000000..01420c5 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityListBoxOption.h" + +#include "AXObjectCache.h" +#include "AccessibilityListBox.h" +#include "Element.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "HTMLOptGroupElement.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "IntRect.h" +#include "RenderListBox.h" +#include "RenderObject.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityListBoxOption::AccessibilityListBoxOption() + : m_optionElement(0) +{ +} + +AccessibilityListBoxOption::~AccessibilityListBoxOption() +{ +} + +PassRefPtr<AccessibilityListBoxOption> AccessibilityListBoxOption::create() +{ + return adoptRef(new AccessibilityListBoxOption()); +} + +bool AccessibilityListBoxOption::isEnabled() const +{ + if (!m_optionElement) + return false; + + if (m_optionElement->hasTagName(optgroupTag)) + return false; + + return true; +} + +bool AccessibilityListBoxOption::isSelected() const +{ + if (!m_optionElement) + return false; + + if (!m_optionElement->hasTagName(optionTag)) + return false; + + return static_cast<HTMLOptionElement*>(m_optionElement)->selected(); +} + +IntRect AccessibilityListBoxOption::elementRect() const +{ + IntRect rect; + if (!m_optionElement) + return rect; + + HTMLSelectElement* listBoxParentNode = listBoxOptionParentNode(); + if (!listBoxParentNode) + return rect; + + RenderObject* listBoxRenderer = listBoxParentNode->renderer(); + if (!listBoxRenderer) + return rect; + + IntRect parentRect = listBoxRenderer->document()->axObjectCache()->getOrCreate(listBoxRenderer)->boundingBoxRect(); + int index = listBoxOptionIndex(); + if (index != -1) + rect = toRenderListBox(listBoxRenderer)->itemBoundingBoxRect(parentRect.x(), parentRect.y(), index); + + return rect; +} + +bool AccessibilityListBoxOption::accessibilityIsIgnored() const +{ + if (!m_optionElement) + return true; + + if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "true")) + return true; + + return parentObject()->accessibilityIsIgnored(); +} + +bool AccessibilityListBoxOption::canSetSelectedAttribute() const +{ + if (!m_optionElement) + return false; + + if (!m_optionElement->hasTagName(optionTag)) + return false; + + if (m_optionElement->disabled()) + return false; + + HTMLSelectElement* selectElement = listBoxOptionParentNode(); + if (selectElement && selectElement->disabled()) + return false; + + return true; +} + +String AccessibilityListBoxOption::stringValue() const +{ + if (!m_optionElement) + return String(); + + const AtomicString& ariaLabel = getAttribute(aria_labelAttr); + if (!ariaLabel.isNull()) + return ariaLabel; + + if (m_optionElement->hasTagName(optionTag)) + return static_cast<HTMLOptionElement*>(m_optionElement)->text(); + + if (m_optionElement->hasTagName(optgroupTag)) + return static_cast<HTMLOptGroupElement*>(m_optionElement)->groupLabelText(); + + return String(); +} + +Element* AccessibilityListBoxOption::actionElement() const +{ + return m_optionElement; +} + +AccessibilityObject* AccessibilityListBoxOption::parentObject() const +{ + HTMLSelectElement* parentNode = listBoxOptionParentNode(); + if (!parentNode) + return 0; + + return m_optionElement->document()->axObjectCache()->getOrCreate(parentNode->renderer()); +} + +void AccessibilityListBoxOption::setSelected(bool selected) +{ + HTMLSelectElement* selectElement = listBoxOptionParentNode(); + if (!selectElement) + return; + + if (!canSetSelectedAttribute()) + return; + + bool isOptionSelected = isSelected(); + if ((isOptionSelected && selected) || (!isOptionSelected && !selected)) + return; + + // Convert from the entire list index to the option index. + int optionIndex = static_cast<SelectElement*>(selectElement)->listToOptionIndex(listBoxOptionIndex()); + selectElement->accessKeySetSelectedIndex(optionIndex); +} + +HTMLSelectElement* AccessibilityListBoxOption::listBoxOptionParentNode() const +{ + if (!m_optionElement) + return 0; + + if (m_optionElement->hasTagName(optionTag)) + return static_cast<HTMLOptionElement*>(m_optionElement)->ownerSelectElement(); + + if (m_optionElement->hasTagName(optgroupTag)) + return static_cast<HTMLOptGroupElement*>(m_optionElement)->ownerSelectElement(); + + return 0; +} + +int AccessibilityListBoxOption::listBoxOptionIndex() const +{ + if (!m_optionElement) + return -1; + + HTMLSelectElement* selectElement = listBoxOptionParentNode(); + if (!selectElement) + return -1; + + const Vector<Element*>& listItems = selectElement->listItems(); + unsigned length = listItems.size(); + for (unsigned i = 0; i < length; i++) + if (listItems[i] == m_optionElement) + return i; + + return -1; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityListBoxOption.h b/Source/WebCore/accessibility/AccessibilityListBoxOption.h new file mode 100644 index 0000000..597c731 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityListBoxOption.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityListBoxOption_h +#define AccessibilityListBoxOption_h + +#include "AccessibilityObject.h" +#include "HTMLElement.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class AccessibilityListBox; +class Element; +class HTMLElement; +class HTMLSelectElement; + +class AccessibilityListBoxOption : public AccessibilityObject { + +private: + AccessibilityListBoxOption(); +public: + static PassRefPtr<AccessibilityListBoxOption> create(); + virtual ~AccessibilityListBoxOption(); + + void setHTMLElement(HTMLElement* element) { m_optionElement = element; } + + virtual AccessibilityRole roleValue() const { return ListBoxOptionRole; } + virtual bool accessibilityIsIgnored() const; + virtual bool isSelected() const; + virtual bool isEnabled() const; + virtual String stringValue() const; + virtual Element* actionElement() const; + virtual Node* node() const { return m_optionElement; } + virtual void setSelected(bool); + virtual bool canSetSelectedAttribute() const; + + virtual IntRect elementRect() const; + virtual AccessibilityObject* parentObject() const; + bool isListBoxOption() const { return true; } + +private: + HTMLElement* m_optionElement; + + virtual bool canHaveChildren() const { return false; } + HTMLSelectElement* listBoxOptionParentNode() const; + int listBoxOptionIndex() const; + IntRect listBoxOptionRect() const; + AccessibilityObject* listBoxOptionAccessibilityObject(HTMLElement*) const; +}; + +} // namespace WebCore + +#endif // AccessibilityListBoxOption_h diff --git a/Source/WebCore/accessibility/AccessibilityMediaControls.cpp b/Source/WebCore/accessibility/AccessibilityMediaControls.cpp new file mode 100644 index 0000000..2f98acd --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMediaControls.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "config.h" + +#if ENABLE(VIDEO) + +#include "AccessibilityMediaControls.h" + +#include "AXObjectCache.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "LocalizedStrings.h" +#include "MediaControlElements.h" +#include "RenderObject.h" +#include "RenderSlider.h" + +namespace WebCore { + +using namespace HTMLNames; + + +AccessibilityMediaControl::AccessibilityMediaControl(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +PassRefPtr<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* renderer) +{ + ASSERT(renderer->node() && renderer->node()->isMediaControlElement()); + + Node* node = renderer->node(); + MediaControlElementType controlType; + + if (node->hasTagName(inputTag)) + controlType = static_cast<MediaControlInputElement*>(node)->displayType(); + else + controlType = static_cast<MediaControlElement*>(node)->displayType(); + + PassRefPtr<AccessibilityObject> obj; + switch (controlType) { + case MediaSlider: + obj = AccessibilityMediaTimeline::create(renderer); + break; + + case MediaCurrentTimeDisplay: + case MediaTimeRemainingDisplay: + obj = AccessibilityMediaTimeDisplay::create(renderer); + break; + + case MediaControlsPanel: + obj = AccessibilityMediaControlsContainer::create(renderer); + break; + + default: + obj = adoptRef(new AccessibilityMediaControl(renderer)); + break; + } + + return obj; +} + +MediaControlElementType AccessibilityMediaControl::controlType() const +{ + if (!renderer() || !renderer()->node()) + return MediaTimelineContainer; // Timeline container is not accessible. + + Node* node = renderer()->node(); + + if (node->hasTagName(inputTag)) + return static_cast<MediaControlInputElement*>(node)->displayType(); + + return static_cast<MediaControlElement*>(node)->displayType(); +} + +String AccessibilityMediaControl::controlTypeName() const +{ + DEFINE_STATIC_LOCAL(const String, mediaFullscreenButtonName, ("FullscreenButton")); + DEFINE_STATIC_LOCAL(const String, mediaMuteButtonName, ("MuteButton")); + DEFINE_STATIC_LOCAL(const String, mediaPlayButtonName, ("PlayButton")); + DEFINE_STATIC_LOCAL(const String, mediaSeekBackButtonName, ("SeekBackButton")); + DEFINE_STATIC_LOCAL(const String, mediaSeekForwardButtonName, ("SeekForwardButton")); + DEFINE_STATIC_LOCAL(const String, mediaRewindButtonName, ("RewindButton")); + DEFINE_STATIC_LOCAL(const String, mediaReturnToRealtimeButtonName, ("ReturnToRealtimeButton")); + DEFINE_STATIC_LOCAL(const String, mediaUnMuteButtonName, ("UnMuteButton")); + DEFINE_STATIC_LOCAL(const String, mediaPauseButtonName, ("PauseButton")); + 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: + return mediaFullscreenButtonName; + case MediaMuteButton: + return mediaMuteButtonName; + case MediaPlayButton: + return mediaPlayButtonName; + case MediaSeekBackButton: + return mediaSeekBackButtonName; + case MediaSeekForwardButton: + return mediaSeekForwardButtonName; + case MediaRewindButton: + return mediaRewindButtonName; + case MediaReturnToRealtimeButton: + return mediaReturnToRealtimeButtonName; + case MediaUnMuteButton: + return mediaUnMuteButtonName; + case MediaPauseButton: + return mediaPauseButtonName; + case MediaStatusDisplay: + return mediaStatusDisplayName; + case MediaCurrentTimeDisplay: + return mediaCurrentTimeDisplay; + case MediaTimeRemainingDisplay: + return mediaTimeRemainingDisplay; + case MediaShowClosedCaptionsButton: + return mediaShowClosedCaptionsButtonName; + case MediaHideClosedCaptionsButton: + return mediaHideClosedCaptionsButtonName; + + default: + break; + } + + return String(); +} + +String AccessibilityMediaControl::title() const +{ + DEFINE_STATIC_LOCAL(const String, controlsPanel, ("ControlsPanel")); + + if (controlType() == MediaControlsPanel) + return localizedMediaControlElementString(controlsPanel); + + return AccessibilityRenderObject::title(); +} + +String AccessibilityMediaControl::accessibilityDescription() const +{ + return localizedMediaControlElementString(controlTypeName()); +} + +String AccessibilityMediaControl::helpText() const +{ + return localizedMediaControlElementHelpText(controlTypeName()); +} + +bool AccessibilityMediaControl::accessibilityIsIgnored() const +{ + if (!m_renderer || !m_renderer->style() || m_renderer->style()->visibility() != VISIBLE || controlType() == MediaTimelineContainer) + return true; + + return false; +} + +AccessibilityRole AccessibilityMediaControl::roleValue() const +{ + switch (controlType()) { + case MediaFullscreenButton: + case MediaMuteButton: + case MediaPlayButton: + case MediaSeekBackButton: + case MediaSeekForwardButton: + case MediaRewindButton: + case MediaReturnToRealtimeButton: + case MediaUnMuteButton: + case MediaPauseButton: + case MediaShowClosedCaptionsButton: + case MediaHideClosedCaptionsButton: + return ButtonRole; + + case MediaStatusDisplay: + return StaticTextRole; + + case MediaTimelineContainer: + return GroupRole; + + default: + break; + } + + return UnknownRole; +} + + + +// +// AccessibilityMediaControlsContainer + +AccessibilityMediaControlsContainer::AccessibilityMediaControlsContainer(RenderObject* renderer) + : AccessibilityMediaControl(renderer) +{ +} + +PassRefPtr<AccessibilityObject> AccessibilityMediaControlsContainer::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityMediaControlsContainer(renderer)); +} + +String AccessibilityMediaControlsContainer::accessibilityDescription() const +{ + return localizedMediaControlElementString(elementTypeName()); +} + +String AccessibilityMediaControlsContainer::helpText() const +{ + return localizedMediaControlElementHelpText(elementTypeName()); +} + +bool AccessibilityMediaControlsContainer::controllingVideoElement() const +{ + if (!m_renderer->node()) + return true; + + MediaControlTimeDisplayElement* element = static_cast<MediaControlTimeDisplayElement*>(m_renderer->node()); + + return element->mediaElement()->isVideo(); +} + +const String AccessibilityMediaControlsContainer::elementTypeName() const +{ + DEFINE_STATIC_LOCAL(const String, videoElement, ("VideoElement")); + DEFINE_STATIC_LOCAL(const String, audioElement, ("AudioElement")); + + if (controllingVideoElement()) + return videoElement; + return audioElement; +} + + +// +// AccessibilityMediaTimeline + +AccessibilityMediaTimeline::AccessibilityMediaTimeline(RenderObject* renderer) + : AccessibilitySlider(renderer) +{ +} + +PassRefPtr<AccessibilityObject> AccessibilityMediaTimeline::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityMediaTimeline(renderer)); +} + +String AccessibilityMediaTimeline::valueDescription() const +{ + ASSERT(m_renderer->node()->hasTagName(inputTag)); + + float time = static_cast<HTMLInputElement*>(m_renderer->node())->value().toFloat(); + return localizedMediaTimeDescription(time); +} + +String AccessibilityMediaTimeline::helpText() const +{ + DEFINE_STATIC_LOCAL(const String, slider, ("Slider")); + return localizedMediaControlElementHelpText(slider); +} + + +// +// AccessibilityMediaTimeDisplay + +AccessibilityMediaTimeDisplay::AccessibilityMediaTimeDisplay(RenderObject* renderer) + : AccessibilityMediaControl(renderer) +{ +} + +PassRefPtr<AccessibilityObject> AccessibilityMediaTimeDisplay::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityMediaTimeDisplay(renderer)); +} + +bool AccessibilityMediaTimeDisplay::accessibilityIsIgnored() const +{ + if (!m_renderer || !m_renderer->style() || m_renderer->style()->visibility() != VISIBLE) + return true; + + return !m_renderer->style()->width().value(); +} + +String AccessibilityMediaTimeDisplay::accessibilityDescription() const +{ + DEFINE_STATIC_LOCAL(const String, currentTimeDisplay, ("CurrentTimeDisplay")); + DEFINE_STATIC_LOCAL(const String, timeRemainingDisplay, ("TimeRemainingDisplay")); + + if (controlType() == MediaCurrentTimeDisplay) + return localizedMediaControlElementString(currentTimeDisplay); + + return localizedMediaControlElementString(timeRemainingDisplay); +} + +String AccessibilityMediaTimeDisplay::stringValue() const +{ + if (!m_renderer || !m_renderer->node()) + return String(); + + MediaControlTimeDisplayElement* element = static_cast<MediaControlTimeDisplayElement*>(m_renderer->node()); + float time = element->currentValue(); + return localizedMediaTimeDescription(fabsf(time)); +} + +} // namespace WebCore + +#endif // ENABLE(VIDEO) diff --git a/Source/WebCore/accessibility/AccessibilityMediaControls.h b/Source/WebCore/accessibility/AccessibilityMediaControls.h new file mode 100644 index 0000000..6f51b2c --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMediaControls.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef AccessibilityMediaControls_h +#define AccessibilityMediaControls_h + +#if ENABLE(VIDEO) + +#include "AccessibilitySlider.h" +#include "MediaControlElements.h" + +namespace WebCore { + +class AccessibilityMediaControl : public AccessibilityRenderObject { + +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaControl() { } + + virtual AccessibilityRole roleValue() const; + virtual bool accessibilityIsIgnored() const; + + virtual String title() const; + virtual String accessibilityDescription() const; + virtual String helpText() const; + +protected: + AccessibilityMediaControl(RenderObject*); + MediaControlElementType controlType() const; + String controlTypeName() const; +}; + + +class AccessibilityMediaTimeline : public AccessibilitySlider { + +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaTimeline() { } + + virtual bool isMediaTimeline() const { return true; } + + virtual String helpText() const; + virtual String valueDescription() const; + const AtomicString& getAttribute(const QualifiedName& attribute) const; + +private: + AccessibilityMediaTimeline(RenderObject*); +}; + + +class AccessibilityMediaControlsContainer : public AccessibilityMediaControl { + +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaControlsContainer() { } + + virtual AccessibilityRole roleValue() const { return ToolbarRole; } + virtual bool accessibilityIsIgnored() const { return false; } + + virtual String helpText() const; + virtual String accessibilityDescription() const; + +private: + AccessibilityMediaControlsContainer(RenderObject*); + bool controllingVideoElement() const; + const String elementTypeName() const; +}; + + +class AccessibilityMediaTimeDisplay : public AccessibilityMediaControl { + +public: + static PassRefPtr<AccessibilityObject> create(RenderObject*); + virtual ~AccessibilityMediaTimeDisplay() { } + + virtual AccessibilityRole roleValue() const { return StaticTextRole; } + virtual bool accessibilityIsIgnored() const; + + virtual String stringValue() const; + virtual String accessibilityDescription() const; + +private: + AccessibilityMediaTimeDisplay(RenderObject*); +}; + + +} // namespace WebCore + +#endif // ENABLE(VIDEO) + +#endif // AccessibilityMediaControls_h diff --git a/Source/WebCore/accessibility/AccessibilityMenuList.cpp b/Source/WebCore/accessibility/AccessibilityMenuList.cpp new file mode 100644 index 0000000..bde4cd4 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMenuList.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityMenuList.h" + +#include "AXObjectCache.h" +#include "AccessibilityMenuListPopup.h" +#include "RenderMenuList.h" + +namespace WebCore { + +AccessibilityMenuList::AccessibilityMenuList(RenderMenuList* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +RenderMenuList* AccessibilityMenuList::renderer() const +{ + return toRenderMenuList(AccessibilityRenderObject::renderer()); +} + +bool AccessibilityMenuList::press() const +{ + RenderMenuList* menuList = static_cast<RenderMenuList*>(m_renderer); + if (menuList->popupIsVisible()) + menuList->hidePopup(); + else + menuList->showPopup(); + return true; +} + +void AccessibilityMenuList::addChildren() +{ + m_haveChildren = true; + + AXObjectCache* cache = m_renderer->document()->axObjectCache(); + + AccessibilityObject* list = cache->getOrCreate(MenuListPopupRole); + if (!list) + return; + + if (list->accessibilityPlatformIncludesObject() == IgnoreObject) { + cache->remove(list->axObjectID()); + return; + } + + static_cast<AccessibilityMenuListPopup*>(list)->setMenuList(this); + m_children.append(list); + + list->addChildren(); +} + +void AccessibilityMenuList::childrenChanged() +{ + if (m_children.isEmpty()) + return; + + ASSERT(m_children.size() == 1); + m_children[0]->childrenChanged(); +} + +bool AccessibilityMenuList::isCollapsed() const +{ + return !static_cast<RenderMenuList*>(m_renderer)->popupIsVisible(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuList.h b/Source/WebCore/accessibility/AccessibilityMenuList.h new file mode 100644 index 0000000..4082f0a --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMenuList.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityMenuList_h +#define AccessibilityMenuList_h + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityMenuList; +class AccessibilityMenuListPopup; +class HTMLOptionElement; +class RenderMenuList; + +class AccessibilityMenuList : public AccessibilityRenderObject { +public: + static PassRefPtr<AccessibilityMenuList> create(RenderMenuList* renderer) { return adoptRef(new AccessibilityMenuList(renderer)); } + + virtual bool isCollapsed() const; + virtual bool press() const; + + RenderMenuList* renderer() const; + +private: + AccessibilityMenuList(RenderMenuList*); + + virtual bool isMenuList() const { return true; } + virtual AccessibilityRole roleValue() const { return PopUpButtonRole; } + virtual bool accessibilityIsIgnored() const { return false; } + virtual bool canSetFocusAttribute() const { return true; } + + virtual void addChildren(); + virtual void childrenChanged(); +}; + +} // namespace WebCore + +#endif // AccessibilityMenuList_h diff --git a/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp b/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp new file mode 100644 index 0000000..5bca580 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityMenuListOption.h" + +#include "AXObjectCache.h" +#include "AccessibilityMenuListPopup.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityMenuListOption::AccessibilityMenuListOption() + : m_popup(0) +{ +} + +void AccessibilityMenuListOption::setElement(HTMLElement* element) +{ + ASSERT_ARG(element, element->hasTagName(optionTag)); + m_element = element; +} + +Element* AccessibilityMenuListOption::actionElement() const +{ + return m_element.get(); +} + +AccessibilityObject* AccessibilityMenuListOption::parentObject() const +{ + return m_popup; +} + +bool AccessibilityMenuListOption::isEnabled() const +{ + // disabled() returns true if the parent <select> element is disabled, + // which we don't want. + return !static_cast<HTMLOptionElement*>(m_element.get())->ownElementDisabled(); +} + +bool AccessibilityMenuListOption::isVisible() const +{ + // In a single-option select with the popup collapsed, only the selected + // item is considered visible. + return !m_popup->isOffScreen() || isSelected(); +} + +bool AccessibilityMenuListOption::isOffScreen() const +{ + // Invisible list options are considered to be offscreen. + return !isVisible(); +} + +bool AccessibilityMenuListOption::isSelected() const +{ + return static_cast<HTMLOptionElement*>(m_element.get())->selected(); +} + +void AccessibilityMenuListOption::setSelected(bool b) +{ + if (!canSetSelectedAttribute()) + return; + + static_cast<HTMLOptionElement*>(m_element.get())->setSelected(b); +} + +String AccessibilityMenuListOption::nameForMSAA() const +{ + return stringValue(); +} + +bool AccessibilityMenuListOption::canSetSelectedAttribute() const +{ + return isEnabled(); +} + +IntRect AccessibilityMenuListOption::elementRect() const +{ + AccessibilityObject* parent = parentObject(); + ASSERT(parent->isMenuListPopup()); + + AccessibilityObject* grandparent = parent->parentObject(); + ASSERT(grandparent->isMenuList()); + + return grandparent->elementRect(); +} + +String AccessibilityMenuListOption::stringValue() const +{ + return static_cast<HTMLOptionElement*>(m_element.get())->text(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuListOption.h b/Source/WebCore/accessibility/AccessibilityMenuListOption.h new file mode 100644 index 0000000..9393d56 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMenuListOption.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityMenuListOption_h +#define AccessibilityMenuListOption_h + +#include "AccessibilityObject.h" + +namespace WebCore { + +class AccessibilityMenuListPopup; +class HTMLElement; + +class AccessibilityMenuListOption : public AccessibilityObject { +public: + static PassRefPtr<AccessibilityMenuListOption> create() { return adoptRef(new AccessibilityMenuListOption); } + + void setElement(HTMLElement*); + void setParent(AccessibilityMenuListPopup* popup) { m_popup = popup; } + +private: + AccessibilityMenuListOption(); + + virtual bool isMenuListOption() const { return true; } + + virtual AccessibilityRole roleValue() const { return MenuListOptionRole; } + virtual bool canHaveChildren() const { return false; } + virtual IntSize size() const { return elementRect().size(); } + + virtual Element* actionElement() const; + virtual AccessibilityObject* parentObject() const; + virtual bool isEnabled() const; + virtual bool isVisible() const; + virtual bool isOffScreen() const; + virtual bool isSelected() const; + virtual String nameForMSAA() const; + virtual void setSelected(bool); + virtual bool canSetSelectedAttribute() const; + virtual IntRect elementRect() const; + virtual String stringValue() const; + + RefPtr<HTMLElement> m_element; + AccessibilityMenuListPopup* m_popup; +}; + +} // namespace WebCore + +#endif // AccessibilityMenuListOption_h diff --git a/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp b/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp new file mode 100644 index 0000000..515e02f --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityMenuListPopup.h" + +#include "AXObjectCache.h" +#include "AccessibilityMenuList.h" +#include "AccessibilityMenuListOption.h" +#include "HTMLNames.h" +#include "HTMLSelectElement.h" +#include "RenderMenuList.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityMenuListPopup::AccessibilityMenuListPopup() + : m_menuList(0) +{ +} + +bool AccessibilityMenuListPopup::isVisible() const +{ + return false; +} + +bool AccessibilityMenuListPopup::isOffScreen() const +{ + return m_menuList->isCollapsed(); +} + +AccessibilityObject* AccessibilityMenuListPopup::parentObject() const +{ + return m_menuList; +} + +bool AccessibilityMenuListPopup::isEnabled() const +{ + return m_menuList->isEnabled(); +} + +AccessibilityMenuListOption* AccessibilityMenuListPopup::menuListOptionAccessibilityObject(HTMLElement* element) const +{ + if (!element || !element->hasTagName(optionTag)) + return 0; + + AccessibilityObject* object = m_menuList->renderer()->document()->axObjectCache()->getOrCreate(MenuListOptionRole); + ASSERT(object->isMenuListOption()); + + AccessibilityMenuListOption* option = static_cast<AccessibilityMenuListOption*>(object); + option->setElement(element); + + return option; +} + +bool AccessibilityMenuListPopup::press() const +{ + m_menuList->press(); + return true; +} + +void AccessibilityMenuListPopup::addChildren() +{ + Node* selectNode = m_menuList->renderer()->node(); + if (!selectNode) + return; + + m_haveChildren = true; + + ASSERT(selectNode->hasTagName(selectTag)); + + const Vector<Element*>& listItems = static_cast<HTMLSelectElement*>(selectNode)->listItems(); + unsigned length = listItems.size(); + for (unsigned i = 0; i < length; i++) { + // The cast to HTMLElement below is safe because the only other possible listItem type + // would be a WMLElement, but WML builds don't use accessbility features at all. + AccessibilityMenuListOption* option = menuListOptionAccessibilityObject(static_cast<HTMLElement*>(listItems[i])); + if (option) { + option->setParent(this); + m_children.append(option); + } + } +} + +void AccessibilityMenuListPopup::childrenChanged() +{ + for (size_t i = m_children.size(); i > 0 ; --i) { + AccessibilityObject* child = m_children[i - 1].get(); + if (child->actionElement() && !child->actionElement()->attached()) { + m_menuList->renderer()->document()->axObjectCache()->remove(child->axObjectID()); + m_children.remove(i - 1); + } + } +} + +void AccessibilityMenuListPopup::setMenuList(AccessibilityMenuList* menuList) +{ + ASSERT_ARG(menuList, menuList); + ASSERT(!m_menuList); + m_menuList = menuList; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuListPopup.h b/Source/WebCore/accessibility/AccessibilityMenuListPopup.h new file mode 100644 index 0000000..88fbf7c --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMenuListPopup.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityMenuListPopup_h +#define AccessibilityMenuListPopup_h + +#include "AccessibilityObject.h" + +namespace WebCore { + +class AccessibilityMenuList; +class AccessibilityMenuListOption; +class HTMLElement; + +class AccessibilityMenuListPopup : public AccessibilityObject { +public: + static PassRefPtr<AccessibilityMenuListPopup> create() { return adoptRef(new AccessibilityMenuListPopup); } + + void setMenuList(AccessibilityMenuList*); + + virtual bool isEnabled() const; + virtual bool isOffScreen() const; + +private: + AccessibilityMenuListPopup(); + + virtual bool isMenuListPopup() const { return true; } + + virtual IntRect elementRect() const { return IntRect(); } + virtual IntSize size() const { return IntSize(); } + virtual AccessibilityRole roleValue() const { return MenuListPopupRole; } + + virtual bool isVisible() const; + virtual AccessibilityObject* parentObject() const; + virtual bool press() const; + virtual void addChildren(); + virtual void childrenChanged(); + + AccessibilityMenuListOption* menuListOptionAccessibilityObject(HTMLElement*) const; + + AccessibilityMenuList* m_menuList; +}; + +} // namespace WebCore + +#endif // AccessibilityMenuListPopup_h diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp new file mode 100644 index 0000000..511bd3f --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -0,0 +1,1071 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityObject.h" + +#include "AXObjectCache.h" +#include "AccessibilityRenderObject.h" +#include "CharacterNames.h" +#include "FloatRect.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "LocalizedStrings.h" +#include "NodeList.h" +#include "NotImplemented.h" +#include "Page.h" +#include "RenderImage.h" +#include "RenderListItem.h" +#include "RenderListMarker.h" +#include "RenderMenuList.h" +#include "RenderTextControl.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "SelectionController.h" +#include "TextIterator.h" +#include "htmlediting.h" +#include "visible_units.h" +#include <wtf/StdLibExtras.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityObject::AccessibilityObject() + : m_id(0) + , m_haveChildren(false) + , m_role(UnknownRole) +#if PLATFORM(GTK) + , m_wrapper(0) +#endif +{ +} + +AccessibilityObject::~AccessibilityObject() +{ + ASSERT(isDetached()); +} + +void AccessibilityObject::detach() +{ +#if HAVE(ACCESSIBILITY) + setWrapper(0); +#endif +} + +AccessibilityObject* AccessibilityObject::parentObjectUnignored() const +{ + AccessibilityObject* parent; + for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { + } + + return parent; +} + +AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) +{ + ASSERT(AXObjectCache::accessibilityEnabled()); + + if (!node) + return 0; + + Document* document = node->document(); + if (!document) + return 0; + + AXObjectCache* cache = document->axObjectCache(); + + AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer()); + while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { + node = node->traverseNextNode(); + + while (node && !node->renderer()) + node = node->traverseNextSibling(); + + if (!node) + return 0; + + accessibleObject = cache->getOrCreate(node->renderer()); + } + + return accessibleObject; +} + +bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) +{ + return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; +} + +bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) +{ + return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole + || ariaRole == ComboBoxRole || ariaRole == SliderRole; +} + +IntPoint AccessibilityObject::clickPoint() const +{ + IntRect rect = elementRect(); + return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); +} + +bool AccessibilityObject::press() const +{ + Element* actionElem = actionElement(); + if (!actionElem) + return false; + if (Frame* f = actionElem->document()->frame()) + f->loader()->resetMultipleFormSubmissionProtection(); + actionElem->accessKeyAction(true); + return true; +} + +String AccessibilityObject::language() const +{ + const AtomicString& lang = getAttribute(langAttr); + if (!lang.isEmpty()) + return lang; + + AccessibilityObject* parent = parentObject(); + + // as a last resort, fall back to the content language specified in the meta tag + if (!parent) { + Document* doc = document(); + if (doc) + return doc->contentLanguage(); + return nullAtom; + } + + return parent->language(); +} + +VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const +{ + if (visiblePos1.isNull() || visiblePos2.isNull()) + return VisiblePositionRange(); + + VisiblePosition startPos; + VisiblePosition endPos; + bool alreadyInOrder; + + // upstream is ordered before downstream for the same position + if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM) + alreadyInOrder = false; + + // use selection order to see if the positions are in order + else + alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst(); + + if (alreadyInOrder) { + startPos = visiblePos1; + endPos = visiblePos2; + } else { + startPos = visiblePos2; + endPos = visiblePos1; + } + + return VisiblePositionRange(startPos, endPos); +} + +VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const +{ + VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary); + VisiblePosition endPosition = endOfWord(startPosition); + return VisiblePositionRange(startPosition, endPosition); +} + +VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const +{ + VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary); + VisiblePosition endPosition = endOfWord(startPosition); + return VisiblePositionRange(startPosition, endPosition); +} + +static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition) +{ + // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line. + // So let's update the position to include that. + VisiblePosition tempPosition; + VisiblePosition startPosition = visiblePosition; + Position p; + RenderObject* renderer; + while (true) { + tempPosition = startPosition.previous(); + if (tempPosition.isNull()) + break; + p = tempPosition.deepEquivalent(); + if (!p.node()) + break; + renderer = p.node()->renderer(); + if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset())) + break; + InlineBox* box; + int ignoredCaretOffset; + p.getInlineBoxAndOffset(tempPosition.affinity(), box, ignoredCaretOffset); + if (box) + break; + startPosition = tempPosition; + } + + return startPosition; +} + +VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePositionRange(); + + // make a caret selection for the position before marker position (to make sure + // we move off of a line start) + VisiblePosition prevVisiblePos = visiblePos.previous(); + if (prevVisiblePos.isNull()) + return VisiblePositionRange(); + + VisiblePosition startPosition = startOfLine(prevVisiblePos); + + // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should + // always be a valid line range. However, startOfLine will return null for position next to a floating object, + // since floating object doesn't really belong to any line. + // This check will reposition the marker before the floating object, to ensure we get a line start. + if (startPosition.isNull()) { + while (startPosition.isNull() && prevVisiblePos.isNotNull()) { + prevVisiblePos = prevVisiblePos.previous(); + startPosition = startOfLine(prevVisiblePos); + } + } else + startPosition = updateAXLineStartForVisiblePosition(startPosition); + + VisiblePosition endPosition = endOfLine(prevVisiblePos); + return VisiblePositionRange(startPosition, endPosition); +} + +VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePositionRange(); + + // make sure we move off of a line end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return VisiblePositionRange(); + + VisiblePosition startPosition = startOfLine(nextVisiblePos); + + // fetch for a valid line start position + if (startPosition.isNull()) { + startPosition = visiblePos; + nextVisiblePos = nextVisiblePos.next(); + } else + startPosition = updateAXLineStartForVisiblePosition(startPosition); + + VisiblePosition endPosition = endOfLine(nextVisiblePos); + + // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position + // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will + // return null for position by a floating object, since floating object doesn't really belong to any line. + // This check will reposition the marker after the floating object, to ensure we get a line end. + while (endPosition.isNull() && nextVisiblePos.isNotNull()) { + nextVisiblePos = nextVisiblePos.next(); + endPosition = endOfLine(nextVisiblePos); + } + + return VisiblePositionRange(startPosition, endPosition); +} + +VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const +{ + // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) + // Related? <rdar://problem/3927736> Text selection broken in 8A336 + VisiblePosition startPosition = startOfSentence(visiblePos); + VisiblePosition endPosition = endOfSentence(startPosition); + return VisiblePositionRange(startPosition, endPosition); +} + +VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const +{ + VisiblePosition startPosition = startOfParagraph(visiblePos); + VisiblePosition endPosition = endOfParagraph(startPosition); + return VisiblePositionRange(startPosition, endPosition); +} + +static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos) +{ + RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer(); + RenderObject* startRenderer = renderer; + RenderStyle* style = renderer->style(); + + // traverse backward by renderer to look for style change + for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { + // skip non-leaf nodes + if (r->firstChild()) + continue; + + // stop at style change + if (r->style() != style) + break; + + // remember match + startRenderer = r; + } + + return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY); +} + +static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos) +{ + RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer(); + RenderObject* endRenderer = renderer; + RenderStyle* style = renderer->style(); + + // traverse forward by renderer to look for style change + for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) { + // skip non-leaf nodes + if (r->firstChild()) + continue; + + // stop at style change + if (r->style() != style) + break; + + // remember match + endRenderer = r; + } + + return lastDeepEditingPositionForNode(endRenderer->node()); +} + +VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePositionRange(); + + return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos)); +} + +// NOTE: Consider providing this utility method as AX API +VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const +{ + unsigned textLength = getLengthForTextRange(); + if (range.start + range.length > textLength) + return VisiblePositionRange(); + + VisiblePosition startPosition = visiblePositionForIndex(range.start); + startPosition.setAffinity(DOWNSTREAM); + VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length); + return VisiblePositionRange(startPosition, endPosition); +} + +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()) + 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()); + if (object->accessibilityIsIgnored()) + return false; + + return true; +} + +// Finds a RenderListItem parent give a node. +static RenderListItem* renderListItemContainerForNode(Node* node) +{ + for (; node; node = node->parentNode()) { + RenderBoxModelObject* renderer = node->renderBoxModelObject(); + if (renderer && renderer->isListItem()) + return toRenderListItem(renderer); + } + return 0; +} + +// Returns the text associated with a list marker if this node is contained within a list item. +String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const +{ + // If the range does not contain the start of the line, the list marker text should not be included. + if (!isStartOfLine(visiblePositionStart)) + return String(); + + RenderListItem* listItem = renderListItemContainerForNode(node); + if (!listItem) + return String(); + + // If this is in a list item, we need to manually add the text for the list marker + // because a RenderListMarker does not have a Node equivalent and thus does not appear + // when iterating text. + const String& markerText = listItem->markerText(); + if (markerText.isEmpty()) + return String(); + + // Append text, plus the period that follows the text. + // FIXME: Not all list marker styles are followed by a period, but this + // sounds much better when there is a synthesized pause because of a period. + Vector<UChar> resultVector; + resultVector.append(markerText.characters(), markerText.length()); + resultVector.append('.'); + resultVector.append(' '); + + return String::adopt(resultVector); +} + +String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const +{ + if (visiblePositionRange.isNull()) + return String(); + + Vector<UChar> resultVector; + 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()) { + // Add a textual representation for list marker text + String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start); + if (!listMarkerText.isEmpty()) + resultVector.append(listMarkerText.characters(), listMarkerText.length()); + + resultVector.append(it.characters(), it.length()); + } else { + // locate the node and starting offset for this replaced range + int exception = 0; + Node* node = it.range()->startContainer(exception); + ASSERT(node == it.range()->endContainer(exception)); + int offset = it.range()->startOffset(exception); + + if (replacedNodeNeedsCharacter(node->childNode(offset))) + resultVector.append(objectReplacementCharacter); + } + } + + return String::adopt(resultVector); +} + +int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const +{ + // FIXME: Multi-byte support + if (visiblePositionRange.isNull()) + return -1; + + int length = 0; + 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()) + length += it.length(); + else { + // locate the node and starting offset for this replaced range + int exception = 0; + Node* node = it.range()->startContainer(exception); + ASSERT(node == it.range()->endContainer(exception)); + int offset = it.range()->startOffset(exception); + + if (replacedNodeNeedsCharacter(node->childNode(offset))) + length++; + } + } + + return length; +} + +VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a word end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return VisiblePosition(); + + return endOfWord(nextVisiblePos, LeftWordIfOnBoundary); +} + +VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a word start + VisiblePosition prevVisiblePos = visiblePos.previous(); + if (prevVisiblePos.isNull()) + return VisiblePosition(); + + return startOfWord(prevVisiblePos, RightWordIfOnBoundary); +} + +VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePosition(); + + // to make sure we move off of a line end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return VisiblePosition(); + + VisiblePosition endPosition = endOfLine(nextVisiblePos); + + // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position + // There are cases like when the position is next to a floating object that'll return null for end of line. This code will avoid returning null. + while (endPosition.isNull() && nextVisiblePos.isNotNull()) { + nextVisiblePos = nextVisiblePos.next(); + endPosition = endOfLine(nextVisiblePos); + } + + return endPosition; +} + +VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a line start + VisiblePosition prevVisiblePos = visiblePos.previous(); + if (prevVisiblePos.isNull()) + return VisiblePosition(); + + VisiblePosition startPosition = startOfLine(prevVisiblePos); + + // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position + // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null. + if (startPosition.isNull()) { + while (startPosition.isNull() && prevVisiblePos.isNotNull()) { + prevVisiblePos = prevVisiblePos.previous(); + startPosition = startOfLine(prevVisiblePos); + } + } else + startPosition = updateAXLineStartForVisiblePosition(startPosition); + + return startPosition; +} + +VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const +{ + // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) + // Related? <rdar://problem/3927736> Text selection broken in 8A336 + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a sentence end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return VisiblePosition(); + + // an empty line is considered a sentence. If it's skipped, then the sentence parser will not + // see this empty line. Instead, return the end position of the empty line. + VisiblePosition endPosition; + + String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get()); + if (lineString.isEmpty()) + endPosition = nextVisiblePos; + else + endPosition = endOfSentence(nextVisiblePos); + + return endPosition; +} + +VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const +{ + // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) + // Related? <rdar://problem/3927736> Text selection broken in 8A336 + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a sentence start + VisiblePosition previousVisiblePos = visiblePos.previous(); + if (previousVisiblePos.isNull()) + return VisiblePosition(); + + // treat empty line as a separate sentence. + VisiblePosition startPosition; + + String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get()); + if (lineString.isEmpty()) + startPosition = previousVisiblePos; + else + startPosition = startOfSentence(previousVisiblePos); + + return startPosition; +} + +VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a paragraph end + VisiblePosition nextPos = visiblePos.next(); + if (nextPos.isNull()) + return VisiblePosition(); + + return endOfParagraph(nextPos); +} + +VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return VisiblePosition(); + + // make sure we move off of a paragraph start + VisiblePosition previousPos = visiblePos.previous(); + if (previousPos.isNull()) + return VisiblePosition(); + + return startOfParagraph(previousPos); +} + +AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return 0; + + RenderObject* obj = visiblePos.deepEquivalent().node()->renderer(); + if (!obj) + return 0; + + return obj->document()->axObjectCache()->getOrCreate(obj); +} + +int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const +{ + if (visiblePos.isNull()) + return 0; + + unsigned lineCount = 0; + VisiblePosition currentVisiblePos = visiblePos; + VisiblePosition savedVisiblePos; + + // move up until we get to the top + // FIXME: This only takes us to the top of the rootEditableElement, not the top of the + // top document. + while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) { + ++lineCount; + savedVisiblePos = currentVisiblePos; + VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0); + currentVisiblePos = prevVisiblePos; + } + + return lineCount - 1; +} + +// NOTE: Consider providing this utility method as AX API +PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const +{ + int index1 = index(positionRange.start); + int index2 = index(positionRange.end); + if (index1 < 0 || index2 < 0 || index1 > index2) + return PlainTextRange(); + + return PlainTextRange(index1, index2 - index1); +} + +// The composed character range in the text associated with this accessibility object that +// is specified by the given screen coordinates. This parameterized attribute returns the +// complete range of characters (including surrogate pairs of multi-byte glyphs) at the given +// screen coordinates. +// NOTE: This varies from AppKit when the point is below the last line. AppKit returns an +// an error in that case. We return textControl->text().length(), 1. Does this matter? +PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const +{ + int i = index(visiblePositionForPoint(point)); + if (i < 0) + return PlainTextRange(); + + return PlainTextRange(i, 1); +} + +// Given a character index, the range of text associated with this accessibility object +// over which the style in effect at that character index applies. +PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const +{ + VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false)); + return plainTextRangeForVisiblePositionRange(range); +} + +// Given an indexed character, the line number of the text associated with this accessibility +// object that contains the character. +unsigned AccessibilityObject::doAXLineForIndex(unsigned index) +{ + return lineForPosition(visiblePositionForIndex(index, false)); +} + +FrameView* AccessibilityObject::documentFrameView() const +{ + const AccessibilityObject* object = this; + while (object && !object->isAccessibilityRenderObject()) + object = object->parentObject(); + + if (!object) + return 0; + + return object->documentFrameView(); +} + +void AccessibilityObject::updateChildrenIfNecessary() +{ + if (!hasChildren()) + addChildren(); +} + +void AccessibilityObject::clearChildren() +{ + m_children.clear(); + m_haveChildren = false; +} + +AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) +{ + RenderObject* obj = node->renderer(); + if (!obj) + return 0; + + RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj); + Element* anchor = axObj->anchorElement(); + if (!anchor) + return 0; + + RenderObject* anchorRenderer = anchor->renderer(); + if (!anchorRenderer) + return 0; + + 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. + DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); + DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); + DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); + DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); + DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); + DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); + DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb())); + DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb())); + 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; + case PopUpButtonRole: + return menuListAction; + case MenuListPopupRole: + return menuListPopupAction; + default: + return noAction; + } +} + +bool AccessibilityObject::ariaIsMultiline() const +{ + return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); +} + +const AtomicString& AccessibilityObject::invalidStatus() const +{ + DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false")); + + // aria-invalid can return false (default), grammer, spelling, or true. + const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr); + + // If empty or not present, it should return false. + if (ariaInvalid.isEmpty()) + return invalidStatusFalse; + + return ariaInvalid; +} + +const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const +{ + Node* elementNode = node(); + if (!elementNode) + return nullAtom; + + if (!elementNode->isElementNode()) + return nullAtom; + + Element* element = static_cast<Element*>(elementNode); + return element->fastGetAttribute(attribute); +} + +// Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; +AccessibilityOrientation AccessibilityObject::orientation() const +{ + IntRect bounds = elementRect(); + if (bounds.size().width() > bounds.size().height()) + return AccessibilityOrientationHorizontal; + if (bounds.size().height() > bounds.size().width()) + return AccessibilityOrientationVertical; + + // 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", ListItemRole }, + { "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", MenuBarRole }, + // "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", PresentationalRole }, + { "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 }, + { "treegrid", TreeGridRole }, + { "treeitem", TreeItemRole } + }; + ARIARoleMap* roleMap = new ARIARoleMap; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++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); +} + +const AtomicString& AccessibilityObject::placeholderValue() const +{ + const AtomicString& placeholder = getAttribute(placeholderAttr); + if (!placeholder.isEmpty()) + return placeholder; + + return nullAtom; +} + +bool AccessibilityObject::isInsideARIALiveRegion() const +{ + if (supportsARIALiveRegion()) + return true; + + for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) { + if (axParent->supportsARIALiveRegion()) + return true; + } + + return false; +} + +bool AccessibilityObject::supportsARIAAttributes() const +{ + return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns(); +} + +bool AccessibilityObject::supportsARIALiveRegion() const +{ + const AtomicString& liveRegion = ariaLiveRegionStatus(); + return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); +} + +AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const +{ + // Send the hit test back into the sub-frame if necessary. + if (isAttachment()) { + Widget* widget = widgetForAttachmentView(); + if (widget && widget->isFrameView()) + return axObjectCache()->getOrCreate(static_cast<ScrollView*>(widget))->accessibilityHitTest(point); + } + + return const_cast<AccessibilityObject*>(this); +} + +AXObjectCache* AccessibilityObject::axObjectCache() const +{ + Document* doc = document(); + if (doc) + return doc->axObjectCache(); + return 0; +} + +AccessibilityObject* AccessibilityObject::focusedUIElement() const +{ + Document* doc = document(); + if (!doc) + return 0; + + Page* page = doc->page(); + if (!page) + return 0; + + return AXObjectCache::focusedUIElementForPage(page); +} + +bool AccessibilityObject::supportsARIAExpanded() const +{ + return !getAttribute(aria_expandedAttr).isEmpty(); +} + +bool AccessibilityObject::isExpanded() const +{ + if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) + return true; + + return false; +} + +AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const +{ + // If this is a real checkbox or radio button, AccessibilityRenderObject will handle. + // If it's an ARIA checkbox or radio, the aria-checked attribute should be used. + + const AtomicString& result = getAttribute(aria_checkedAttr); + if (equalIgnoringCase(result, "true")) + return ButtonStateOn; + if (equalIgnoringCase(result, "mixed")) + return ButtonStateMixed; + + return ButtonStateOff; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h new file mode 100644 index 0000000..45b54fc --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityObject.h @@ -0,0 +1,614 @@ +/* + * Copyright (C) 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nuanti Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityObject_h +#define AccessibilityObject_h + +#include "IntRect.h" +#include "Range.h" +#include "VisiblePosition.h" +#include "VisibleSelection.h" +#include <wtf/Forward.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +#if PLATFORM(MAC) +#include <wtf/RetainPtr.h> +#elif PLATFORM(WIN) && !OS(WINCE) +#include "AccessibilityObjectWrapperWin.h" +#include "COMPtr.h" +#elif PLATFORM(CHROMIUM) +#include "AccessibilityObjectWrapper.h" +#endif + +typedef struct _NSRange NSRange; + +#ifdef __OBJC__ +@class AccessibilityObjectWrapper; +@class NSArray; +@class NSAttributedString; +@class NSData; +@class NSMutableAttributedString; +@class NSString; +@class NSValue; +@class NSView; +#else +class NSArray; +class NSAttributedString; +class NSData; +class NSMutableAttributedString; +class NSString; +class NSValue; +class NSView; +#if PLATFORM(GTK) +typedef struct _AtkObject AtkObject; +typedef struct _AtkObject AccessibilityObjectWrapper; +#else +class AccessibilityObjectWrapper; +#endif +#endif + +namespace WebCore { + +class AXObjectCache; +class Element; +class Frame; +class FrameView; +class HTMLAnchorElement; +class HTMLAreaElement; +class IntPoint; +class IntSize; +class Node; +class RenderObject; +class RenderListItem; +class VisibleSelection; +class Widget; + +typedef unsigned AXID; + +enum AccessibilityRole { + UnknownRole = 1, + ButtonRole, + RadioButtonRole, + CheckBoxRole, + SliderRole, + TabGroupRole, + TextFieldRole, + StaticTextRole, + TextAreaRole, + ScrollAreaRole, + PopUpButtonRole, + MenuButtonRole, + TableRole, + ApplicationRole, + GroupRole, + RadioGroupRole, + ListRole, + ScrollBarRole, + ValueIndicatorRole, + ImageRole, + MenuBarRole, + MenuRole, + MenuItemRole, + ColumnRole, + RowRole, + ToolbarRole, + BusyIndicatorRole, + ProgressIndicatorRole, + WindowRole, + DrawerRole, + SystemWideRole, + OutlineRole, + IncrementorRole, + BrowserRole, + ComboBoxRole, + SplitGroupRole, + SplitterRole, + ColorWellRole, + GrowAreaRole, + SheetRole, + HelpTagRole, + MatteRole, + RulerRole, + RulerMarkerRole, + LinkRole, + DisclosureTriangleRole, + GridRole, + CellRole, + ColumnHeaderRole, + RowHeaderRole, + // AppKit includes SortButtonRole but it is misnamed and really a subrole of ButtonRole so we do not include it here. + + // WebCore-specific roles + WebCoreLinkRole, + ImageMapLinkRole, + ImageMapRole, + ListMarkerRole, + WebAreaRole, + HeadingRole, + ListBoxRole, + ListBoxOptionRole, + TableHeaderContainerRole, + DefinitionListTermRole, + DefinitionListDefinitionRole, + AnnotationRole, + SliderThumbRole, + IgnoredRole, + PresentationalRole, + TabRole, + TabListRole, + TabPanelRole, + TreeRole, + TreeGridRole, + TreeItemRole, + DirectoryRole, + EditableTextRole, + ListItemRole, + MenuListPopupRole, + MenuListOptionRole, + + // ARIA Grouping roles + LandmarkApplicationRole, + LandmarkBannerRole, + LandmarkComplementaryRole, + LandmarkContentInfoRole, + LandmarkMainRole, + LandmarkNavigationRole, + LandmarkSearchRole, + + ApplicationAlertRole, + ApplicationAlertDialogRole, + ApplicationDialogRole, + ApplicationLogRole, + ApplicationMarqueeRole, + ApplicationStatusRole, + ApplicationTimerRole, + + DocumentRole, + DocumentArticleRole, + DocumentMathRole, + DocumentNoteRole, + DocumentRegionRole, + + UserInterfaceTooltipRole +}; + +enum AccessibilityOrientation { + AccessibilityOrientationVertical, + AccessibilityOrientationHorizontal, +}; + +enum AccessibilityObjectInclusion { + IncludeObject, + IgnoreObject, + DefaultBehavior, +}; + +enum AccessibilityButtonState { + ButtonStateOff = 0, + ButtonStateOn, + ButtonStateMixed, +}; + +struct VisiblePositionRange { + + VisiblePosition start; + VisiblePosition end; + + VisiblePositionRange() {} + + VisiblePositionRange(const VisiblePosition& s, const VisiblePosition& e) + : start(s) + , end(e) + { } + + bool isNull() const { return start.isNull() || end.isNull(); } +}; + +struct PlainTextRange { + + unsigned start; + unsigned length; + + PlainTextRange() + : start(0) + , length(0) + { } + + PlainTextRange(unsigned s, unsigned l) + : start(s) + , length(l) + { } + + bool isNull() const { return !start && !length; } +}; + +class AccessibilityObject : public RefCounted<AccessibilityObject> { +protected: + AccessibilityObject(); +public: + virtual ~AccessibilityObject(); + virtual void detach(); + + typedef Vector<RefPtr<AccessibilityObject> > AccessibilityChildrenVector; + + virtual bool isAccessibilityRenderObject() const { return false; } + virtual bool isAccessibilityScrollbar() const { return false; } + virtual bool isAccessibilityScrollView() 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 isNativeTextControl() const { return false; } + virtual bool isWebArea() const { return false; } + virtual bool isCheckbox() const { return roleValue() == CheckBoxRole; } + virtual bool isRadioButton() const { return roleValue() == RadioButtonRole; } + 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 isInputImage() const { return false; } + virtual bool isProgressIndicator() const { return false; } + virtual bool isSlider() const { return false; } + virtual bool isInputSlider() const { return false; } + virtual bool isControl() const { return false; } + virtual bool isList() const { return false; } + virtual bool isAccessibilityTable() 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 isARIATreeGridRow() const { return false; } + virtual bool isImageMapLink() const { return false; } + virtual bool isMenuList() const { return false; } + virtual bool isMenuListPopup() const { return false; } + virtual bool isMenuListOption() const { return false; } + bool isTextControl() const { return roleValue() == TextAreaRole || roleValue() == TextFieldRole; } + 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; } + bool isListItem() const { return roleValue() == ListItemRole; } + bool isCheckboxOrRadio() const { return isCheckbox() || isRadioButton(); } + bool isScrollView() const { return roleValue() == ScrollAreaRole; } + + 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 isMultiSelectable() 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; + virtual bool isVisible() const { return true; } + virtual bool isCollapsed() 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 canSetNumericValue() const { return false; } + virtual bool canSetSelectedAttribute() const { return false; } + virtual bool canSetSelectedChildrenAttribute() const { return false; } + virtual bool canSetExpandedAttribute() const { return false; } + + // A programmatic way to set a name on an AccessibleObject. + virtual void setAccessibleName(String&) { } + + virtual Node* node() const { return 0; } + bool accessibilityShouldUseUniqueId() const { return true; } + virtual bool accessibilityIsIgnored() const { return true; } + + virtual int headingLevel() const { return 0; } + virtual AccessibilityButtonState checkboxOrRadioValue() const; + virtual String valueDescription() const { return String(); } + virtual float valueForRange() const { return 0.0f; } + 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; } + virtual double estimatedLoadingProgress() 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 { } + virtual bool ariaHasPopup() const { return false; } + bool ariaIsMultiline() const; + virtual const AtomicString& invalidStatus() const; + bool supportsARIAExpanded() const; + + // ARIA drag and drop + virtual bool supportsARIADropping() const { return false; } + virtual bool supportsARIADragging() const { return false; } + virtual bool isARIAGrabbed() { return false; } + virtual void setARIAGrabbed(bool) { } + virtual void determineARIADropEffects(Vector<String>&) { } + + // Called on the root AX object to return the deepest available element. + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const { return 0; } + // Called on the AX object after the render tree determines which is the right AccessibilityRenderObject. + virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; + + virtual AccessibilityObject* focusedUIElement() const; + + virtual AccessibilityObject* firstChild() const { return 0; } + virtual AccessibilityObject* lastChild() const { return 0; } + virtual AccessibilityObject* previousSibling() const { return 0; } + virtual AccessibilityObject* nextSibling() const { return 0; } + virtual AccessibilityObject* parentObject() const = 0; + virtual AccessibilityObject* parentObjectUnignored() const; + virtual AccessibilityObject* parentObjectIfExists() const { return 0; } + static AccessibilityObject* firstAccessibleObjectFromNode(const Node*); + + virtual AccessibilityObject* observableObject() const { return 0; } + virtual void linkedUIElements(AccessibilityChildrenVector&) const { } + virtual AccessibilityObject* titleUIElement() const { return 0; } + virtual bool exposesTitleUIElement() const { return true; } + virtual AccessibilityObject* correspondingControlForLabelElement() const { return 0; } + virtual AccessibilityObject* scrollBar(AccessibilityOrientation) const { return 0; } + + virtual AccessibilityRole ariaRoleAttribute() const { return UnknownRole; } + virtual bool isPresentationalChildOfAriaRole() const { return false; } + virtual bool ariaRoleHasPresentationalChildren() const { return false; } + + void setRoleValue(AccessibilityRole role) { m_role = role; } + virtual AccessibilityRole roleValue() const { return m_role; } + virtual String ariaLabeledByAttribute() const { return String(); } + virtual String ariaDescribedByAttribute() const { return String(); } + virtual String accessibilityDescription() const { return String(); } + + virtual AXObjectCache* axObjectCache() const; + AXID axObjectID() const { return m_id; } + void setAXObjectID(AXID axObjectID) { m_id = axObjectID; } + + static AccessibilityObject* anchorElementForNode(Node*); + virtual Element* anchorElement() const { return 0; } + virtual Element* actionElement() const { return 0; } + virtual IntRect boundingBoxRect() const { return IntRect(); } + virtual IntRect elementRect() const = 0; + virtual IntSize size() const { return elementRect().size(); } + virtual IntPoint clickPoint() const; + + virtual PlainTextRange selectedTextRange() const { return PlainTextRange(); } + unsigned selectionStart() const { return selectedTextRange().start; } + unsigned selectionEnd() const { return selectedTextRange().length; } + + virtual KURL url() const { return KURL(); } + virtual VisibleSelection selection() const { return VisibleSelection(); } + virtual String stringValue() const { return String(); } + virtual String title() const { return String(); } + virtual String helpText() const { return String(); } + virtual String textUnderElement() const { return String(); } + virtual String text() const { return String(); } + virtual int textLength() const { return 0; } + virtual String selectedText() const { return String(); } + virtual const AtomicString& accessKey() const { return nullAtom; } + const String& actionVerb() const; + virtual Widget* widget() const { return 0; } + virtual Widget* widgetForAttachmentView() const { return 0; } + virtual Document* document() const { return 0; } + virtual FrameView* topDocumentFrameView() const { return 0; } + virtual FrameView* documentFrameView() const; + String language() const; + virtual unsigned hierarchicalLevel() const { return 0; } + const AtomicString& placeholderValue() const; + + virtual void setFocused(bool) { } + virtual void setSelectedText(const String&) { } + virtual void setSelectedTextRange(const PlainTextRange&) { } + virtual void setValue(const String&) { } + virtual void setValue(float) { } + 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 childrenChanged() { } + virtual void contentChanged() { } + virtual const AccessibilityChildrenVector& children() { return m_children; } + virtual void addChildren() { } + virtual bool canHaveChildren() const { return true; } + virtual bool hasChildren() const { return m_haveChildren; } + virtual void updateChildrenIfNecessary(); + 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() { } + virtual void handleAriaExpandedChanged() { } + + static AccessibilityRole ariaRoleToWebCoreRole(const String&); + const AtomicString& getAttribute(const QualifiedName&) const; + + virtual VisiblePositionRange visiblePositionRange() const { return VisiblePositionRange(); } + virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); } + + VisiblePositionRange visiblePositionRangeForUnorderedPositions(const VisiblePosition&, const VisiblePosition&) const; + VisiblePositionRange positionOfLeftWord(const VisiblePosition&) const; + VisiblePositionRange positionOfRightWord(const VisiblePosition&) const; + VisiblePositionRange leftLineVisiblePositionRange(const VisiblePosition&) const; + VisiblePositionRange rightLineVisiblePositionRange(const VisiblePosition&) const; + VisiblePositionRange sentenceForPosition(const VisiblePosition&) const; + VisiblePositionRange paragraphForPosition(const VisiblePosition&) const; + VisiblePositionRange styleRangeForPosition(const VisiblePosition&) const; + VisiblePositionRange visiblePositionRangeForRange(const PlainTextRange&) const; + + String stringForVisiblePositionRange(const VisiblePositionRange&) const; + virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const { return IntRect(); } + int lengthForVisiblePositionRange(const VisiblePositionRange&) const; + virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const { } + + virtual VisiblePosition visiblePositionForPoint(const IntPoint&) const { return VisiblePosition(); } + VisiblePosition nextVisiblePosition(const VisiblePosition& visiblePos) const { return visiblePos.next(); } + VisiblePosition previousVisiblePosition(const VisiblePosition& visiblePos) const { return visiblePos.previous(); } + VisiblePosition nextWordEnd(const VisiblePosition&) const; + VisiblePosition previousWordStart(const VisiblePosition&) const; + VisiblePosition nextLineEndPosition(const VisiblePosition&) const; + VisiblePosition previousLineStartPosition(const VisiblePosition&) const; + VisiblePosition nextSentenceEndPosition(const VisiblePosition&) const; + VisiblePosition previousSentenceStartPosition(const VisiblePosition&) const; + VisiblePosition nextParagraphEndPosition(const VisiblePosition&) const; + VisiblePosition previousParagraphStartPosition(const VisiblePosition&) const; + virtual VisiblePosition visiblePositionForIndex(unsigned, bool /*lastIndexOK */) const { return VisiblePosition(); } + + virtual VisiblePosition visiblePositionForIndex(int) const { return VisiblePosition(); } + virtual int indexForVisiblePosition(const VisiblePosition&) const { return 0; } + + AccessibilityObject* accessibilityObjectForPosition(const VisiblePosition&) const; + int lineForPosition(const VisiblePosition&) const; + PlainTextRange plainTextRangeForVisiblePositionRange(const VisiblePositionRange&) const; + virtual int index(const VisiblePosition&) const { return -1; } + + virtual PlainTextRange doAXRangeForLine(unsigned) const { return PlainTextRange(); } + PlainTextRange doAXRangeForPosition(const IntPoint&) const; + virtual PlainTextRange doAXRangeForIndex(unsigned) const { return PlainTextRange(); } + PlainTextRange doAXStyleRangeForIndex(unsigned) const; + + virtual String doAXStringForRange(const PlainTextRange&) const { return String(); } + virtual IntRect doAXBoundsForRange(const PlainTextRange&) const { return IntRect(); } + String listMarkerTextForNodeAndPosition(Node*, const VisiblePosition&) const; + + 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(); } + virtual AccessibilityRole roleValueForMSAA() const { return roleValue(); } + + // 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&); + + // ARIA live-region features. + bool supportsARIALiveRegion() const; + bool isInsideARIALiveRegion() const; + virtual const AtomicString& ariaLiveRegionStatus() const { return nullAtom; } + virtual const AtomicString& ariaLiveRegionRelevant() const { return nullAtom; } + virtual bool ariaLiveRegionAtomic() const { return false; } + virtual bool ariaLiveRegionBusy() const { return false; } + + bool supportsARIAAttributes() const; + + // CSS3 Speech properties. + virtual ESpeak speakProperty() const { return SpeakNormal; } + +#if HAVE(ACCESSIBILITY) +#if PLATFORM(GTK) + AccessibilityObjectWrapper* wrapper() const; + void setWrapper(AccessibilityObjectWrapper*); +#else + AccessibilityObjectWrapper* wrapper() const { return m_wrapper.get(); } + void setWrapper(AccessibilityObjectWrapper* wrapper) + { + m_wrapper = wrapper; + } +#endif +#endif + +#if HAVE(ACCESSIBILITY) + // a platform-specific method for determining if an attachment is ignored + bool accessibilityIgnoreAttachment() const; + // gives platforms the opportunity to indicate if and how an object should be included + AccessibilityObjectInclusion accessibilityPlatformIncludesObject() const; +#else + bool accessibilityIgnoreAttachment() const { return true; } + AccessibilityObjectInclusion accessibilityPlatformIncludesObject() const { return DefaultBehavior; } +#endif + + // allows for an AccessibilityObject to update its render tree or perform + // other operations update type operations + virtual void updateBackingStore() { } + +protected: + AXID m_id; + AccessibilityChildrenVector m_children; + mutable bool m_haveChildren; + AccessibilityRole m_role; + + virtual void clearChildren(); + virtual bool isDetached() const { return true; } + +#if PLATFORM(GTK) + bool allowsTextRanges() const; + unsigned getLengthForTextRange() const; +#else + bool allowsTextRanges() const { return isTextControl(); } + unsigned getLengthForTextRange() const { return text().length(); } +#endif + +#if PLATFORM(MAC) + RetainPtr<AccessibilityObjectWrapper> m_wrapper; +#elif PLATFORM(WIN) && !OS(WINCE) + COMPtr<AccessibilityObjectWrapper> m_wrapper; +#elif PLATFORM(GTK) + AtkObject* m_wrapper; +#elif PLATFORM(CHROMIUM) + RefPtr<AccessibilityObjectWrapper> m_wrapper; +#endif +}; + +} // namespace WebCore + +#endif // AccessibilityObject_h diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp new file mode 100644 index 0000000..c7202f0 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#if ENABLE(PROGRESS_TAG) + +#include "AccessibilityProgressIndicator.h" + +#include "FloatConversion.h" +#include "HTMLNames.h" +#include "HTMLProgressElement.h" +#include "RenderObject.h" +#include "RenderProgress.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderProgress* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +PassRefPtr<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderProgress* renderer) +{ + return adoptRef(new AccessibilityProgressIndicator(renderer)); +} + +bool AccessibilityProgressIndicator::accessibilityIsIgnored() const +{ + return accessibilityIsIgnoredBase() == IgnoreObject; +} + +float AccessibilityProgressIndicator::valueForRange() const +{ + if (element()->position() >= 0) + return narrowPrecisionToFloat(element()->value()); + // Indeterminate progress bar should return 0. + return 0.0f; +} + +float AccessibilityProgressIndicator::maxValueForRange() const +{ + return narrowPrecisionToFloat(element()->max()); +} + +float AccessibilityProgressIndicator::minValueForRange() const +{ + return 0.0f; +} + +HTMLProgressElement* AccessibilityProgressIndicator::element() const +{ + return toRenderProgress(m_renderer)->progressElement(); +} + + +} // namespace WebCore + +#endif // ENABLE(PROGRESS_TAG) diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.h b/Source/WebCore/accessibility/AccessibilityProgressIndicator.h new file mode 100644 index 0000000..b52a619 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef AccessibilityProgressIndicator_h +#define AccessibilityProgressIndicator_h + +#if ENABLE(PROGRESS_TAG) + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class HTMLProgressElement; +class RenderProgress; + +class AccessibilityProgressIndicator : public AccessibilityRenderObject { +public: + static PassRefPtr<AccessibilityProgressIndicator> create(RenderProgress*); + +private: + virtual AccessibilityRole roleValue() const { return ProgressIndicatorRole; } + + virtual bool isProgressIndicator() const { return true; } + + virtual float valueForRange() const; + virtual float maxValueForRange() const; + virtual float minValueForRange() const; + + AccessibilityProgressIndicator(RenderProgress*); + + HTMLProgressElement* element() const; + virtual bool accessibilityIsIgnored() const; +}; + + +} // namespace WebCore + +#endif // ENABLE(PROGRESS_TAG) + +#endif // AccessibilityProgressIndicator_h diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp new file mode 100644 index 0000000..7dc6840 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp @@ -0,0 +1,3688 @@ +/* +* Copyright (C) 2008 Apple Inc. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +* its contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "config.h" +#include "AccessibilityRenderObject.h" + +#include "AXObjectCache.h" +#include "AccessibilityImageMapLink.h" +#include "AccessibilityListBox.h" +#include "CharacterNames.h" +#include "EventNames.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "HTMLAreaElement.h" +#include "HTMLFormElement.h" +#include "HTMLFrameElementBase.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLLabelElement.h" +#include "HTMLMapElement.h" +#include "HTMLOptGroupElement.h" +#include "HTMLOptionElement.h" +#include "HTMLOptionsCollection.h" +#include "HTMLSelectElement.h" +#include "HTMLTextAreaElement.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "LocalizedStrings.h" +#include "NodeList.h" +#include "ProgressTracker.h" +#include "RenderButton.h" +#include "RenderFieldset.h" +#include "RenderFileUploadControl.h" +#include "RenderHTMLCanvas.h" +#include "RenderImage.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderListBox.h" +#include "RenderListMarker.h" +#include "RenderMenuList.h" +#include "RenderText.h" +#include "RenderTextControl.h" +#include "RenderTextFragment.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "SelectElement.h" +#include "SelectionController.h" +#include "Text.h" +#include "TextIterator.h" +#include "htmlediting.h" +#include "visible_units.h" +#include <wtf/StdLibExtras.h> + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer) + : AccessibilityObject() + , m_renderer(renderer) + , m_ariaRole(UnknownRole) + , m_childrenDirty(false) + , m_roleForMSAA(UnknownRole) +{ + m_role = determineAccessibilityRole(); + +#ifndef NDEBUG + m_renderer->setHasAXObject(true); +#endif +} + +AccessibilityRenderObject::~AccessibilityRenderObject() +{ + ASSERT(isDetached()); +} + +PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityRenderObject(renderer)); +} + +void AccessibilityRenderObject::detach() +{ + clearChildren(); + AccessibilityObject::detach(); + +#ifndef NDEBUG + if (m_renderer) + m_renderer->setHasAXObject(false); +#endif + m_renderer = 0; +} + +RenderBoxModelObject* AccessibilityRenderObject::renderBoxModelObject() const +{ + if (!m_renderer || !m_renderer->isBoxModelObject()) + return 0; + return toRenderBoxModelObject(m_renderer); +} + +static inline bool isInlineWithContinuation(RenderObject* object) +{ + if (!object->isBoxModelObject()) + return false; + + RenderBoxModelObject* renderer = toRenderBoxModelObject(object); + if (!renderer->isRenderInline()) + return false; + + return toRenderInline(renderer)->continuation(); +} + +static inline RenderObject* firstChildInContinuation(RenderObject* renderer) +{ + RenderObject* r = toRenderInline(renderer)->continuation(); + + while (r) { + if (r->isRenderBlock()) + return r; + if (RenderObject* child = r->firstChild()) + return child; + r = toRenderInline(r)->continuation(); + } + + return 0; +} + +static inline RenderObject* firstChildConsideringContinuation(RenderObject* renderer) +{ + RenderObject* firstChild = renderer->firstChild(); + + if (!firstChild && isInlineWithContinuation(renderer)) + firstChild = firstChildInContinuation(renderer); + + return firstChild; +} + + +static inline RenderObject* lastChildConsideringContinuation(RenderObject* renderer) +{ + RenderObject* lastChild = renderer->lastChild(); + RenderObject* prev; + RenderObject* cur = renderer; + + if (!cur->isRenderInline() && !cur->isRenderBlock()) + return renderer; + + while (cur) { + prev = cur; + + if (RenderObject* lc = cur->lastChild()) + lastChild = lc; + + if (cur->isRenderInline()) { + cur = toRenderInline(cur)->inlineElementContinuation(); + ASSERT(cur || !toRenderInline(prev)->continuation()); + } else + cur = toRenderBlock(cur)->inlineElementContinuation(); + } + + return lastChild; +} + +AccessibilityObject* AccessibilityRenderObject::firstChild() const +{ + if (!m_renderer) + return 0; + + RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); + + if (!firstChild) + return 0; + + return axObjectCache()->getOrCreate(firstChild); +} + +AccessibilityObject* AccessibilityRenderObject::lastChild() const +{ + if (!m_renderer) + return 0; + + RenderObject* lastChild = lastChildConsideringContinuation(m_renderer); + + if (!lastChild) + return 0; + + return axObjectCache()->getOrCreate(lastChild); +} + +static inline RenderInline* startOfContinuations(RenderObject* r) +{ + if (r->isInlineElementContinuation()) + return toRenderInline(r->node()->renderer()); + + // Blocks with a previous continuation always have a next continuation + if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) + return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->node()->renderer()); + + return 0; +} + +static inline RenderObject* endOfContinuations(RenderObject* renderer) +{ + RenderObject* prev = renderer; + RenderObject* cur = renderer; + + if (!cur->isRenderInline() && !cur->isRenderBlock()) + return renderer; + + while (cur) { + prev = cur; + if (cur->isRenderInline()) { + cur = toRenderInline(cur)->inlineElementContinuation(); + ASSERT(cur || !toRenderInline(prev)->continuation()); + } else + cur = toRenderBlock(cur)->inlineElementContinuation(); + } + + return prev; +} + + +static inline RenderObject* childBeforeConsideringContinuations(RenderInline* r, RenderObject* child) +{ + RenderBoxModelObject* curContainer = r; + RenderObject* cur = 0; + RenderObject* prev = 0; + + while (curContainer) { + if (curContainer->isRenderInline()) { + cur = curContainer->firstChild(); + while (cur) { + if (cur == child) + return prev; + prev = cur; + cur = cur->nextSibling(); + } + + curContainer = toRenderInline(curContainer)->continuation(); + } else if (curContainer->isRenderBlock()) { + if (curContainer == child) + return prev; + + prev = curContainer; + curContainer = toRenderBlock(curContainer)->inlineElementContinuation(); + } + } + + ASSERT_NOT_REACHED(); + + return 0; +} + +static inline bool firstChildIsInlineContinuation(RenderObject* renderer) +{ + return renderer->firstChild() && renderer->firstChild()->isInlineElementContinuation(); +} + +AccessibilityObject* AccessibilityRenderObject::previousSibling() const +{ + if (!m_renderer) + return 0; + + RenderObject* previousSibling = 0; + + // Case 1: The node is a block and is an inline's continuation. In that case, the inline's + // last child is our previous sibling (or further back in the continuation chain) + RenderInline* startOfConts; + if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer); + + // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before + // the parent of the start, since everything in between will be linked up via the continuation. + else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(m_renderer)) + previousSibling = startOfContinuations(m_renderer->firstChild())->parent()->previousSibling(); + + // Case 3: The node has an actual previous sibling + else if (RenderObject* ps = m_renderer->previousSibling()) + previousSibling = ps; + + // Case 4: This node has no previous siblings, but its parent is an inline, + // and is another node's inline continutation. Follow the continuation chain. + else if (m_renderer->parent()->isRenderInline() && (startOfConts = startOfContinuations(m_renderer->parent()))) + previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer->parent()->firstChild()); + + if (!previousSibling) + return 0; + + return axObjectCache()->getOrCreate(previousSibling); +} + +static inline bool lastChildHasContinuation(RenderObject* renderer) +{ + return renderer->lastChild() && isInlineWithContinuation(renderer->lastChild()); +} + +AccessibilityObject* AccessibilityRenderObject::nextSibling() const +{ + if (!m_renderer) + return 0; + + RenderObject* nextSibling = 0; + + // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's + // first child. + RenderInline* inlineContinuation; + if (m_renderer->isRenderBlock() && (inlineContinuation = toRenderBlock(m_renderer)->inlineElementContinuation())) + nextSibling = firstChildConsideringContinuation(inlineContinuation); + + // Case 2: Anonymous block parent of the start of a continuation - skip all the way to + // after the parent of the end, since everything in between will be linked up via the continuation. + else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_renderer)) + nextSibling = endOfContinuations(m_renderer->lastChild())->parent()->nextSibling(); + + // Case 3: node has an actual next sibling + else if (RenderObject* ns = m_renderer->nextSibling()) + nextSibling = ns; + + // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end + // of the continuation chain. + else if (isInlineWithContinuation(m_renderer)) + nextSibling = endOfContinuations(m_renderer)->nextSibling(); + + // Case 5: node has no next sibling, and its parent is an inline with a continuation. + else if (isInlineWithContinuation(m_renderer->parent())) { + RenderObject* continuation = toRenderInline(m_renderer->parent())->continuation(); + + // Case 5a: continuation is a block - in this case the block itself is the next sibling. + if (continuation->isRenderBlock()) + nextSibling = continuation; + // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling + else + nextSibling = firstChildConsideringContinuation(continuation); + } + + if (!nextSibling) + return 0; + + return axObjectCache()->getOrCreate(nextSibling); +} + +static RenderBoxModelObject* nextContinuation(RenderObject* renderer) +{ + ASSERT(renderer); + if (renderer->isRenderInline() && !renderer->isReplaced()) + return toRenderInline(renderer)->continuation(); + if (renderer->isRenderBlock()) + return toRenderBlock(renderer)->inlineElementContinuation(); + return 0; +} + +RenderObject* AccessibilityRenderObject::renderParentObject() const +{ + if (!m_renderer) + return 0; + + RenderObject* parent = m_renderer->parent(); + + // Case 1: node is a block and is an inline's continuation. Parent + // is the start of the continuation chain. + RenderObject* startOfConts = 0; + RenderObject* firstChild = 0; + if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + parent = startOfConts; + + // Case 2: node's parent is an inline which is some node's continuation; parent is + // the earliest node in the continuation chain. + else if (parent && parent->isRenderInline() && (startOfConts = startOfContinuations(parent))) + parent = startOfConts; + + // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation. + else if (parent && (firstChild = parent->firstChild()) && firstChild->node()) { + // Get the node's renderer and follow that continuation chain until the first child is found + RenderObject* nodeRenderFirstChild = firstChild->node()->renderer(); + if (nodeRenderFirstChild != firstChild) { + for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(contsTest)) { + if (contsTest == firstChild) { + parent = nodeRenderFirstChild->parent(); + break; + } + } + } + } + + return parent; +} + +AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const +{ + return axObjectCache()->get(renderParentObject()); +} + +AccessibilityObject* AccessibilityRenderObject::parentObject() const +{ + if (!m_renderer) + return 0; + + if (ariaRoleAttribute() == MenuBarRole) + return axObjectCache()->getOrCreate(m_renderer->parent()); + + // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child + if (ariaRoleAttribute() == MenuRole) { + AccessibilityObject* parent = menuButtonForMenu(); + if (parent) + return parent; + } + + RenderObject* parentObj = renderParentObject(); + if (parentObj) + return axObjectCache()->getOrCreate(parentObj); + + // WebArea's parent should be the scroll view containing it. + if (isWebArea()) + return axObjectCache()->getOrCreate(m_renderer->frame()->view()); + + return 0; +} + +bool AccessibilityRenderObject::isWebArea() const +{ + return roleValue() == WebAreaRole; +} + +bool AccessibilityRenderObject::isImageButton() const +{ + return isNativeImage() && roleValue() == ButtonRole; +} + +bool AccessibilityRenderObject::isAnchor() const +{ + return !isNativeImage() && isLink(); +} + +bool AccessibilityRenderObject::isNativeTextControl() const +{ + return m_renderer->isTextControl(); +} + +bool AccessibilityRenderObject::isNativeImage() const +{ + return m_renderer->isBoxModelObject() && toRenderBoxModelObject(m_renderer)->isImage(); +} + +bool AccessibilityRenderObject::isImage() const +{ + return roleValue() == ImageRole; +} + +bool AccessibilityRenderObject::isAttachment() const +{ + RenderBoxModelObject* renderer = renderBoxModelObject(); + if (!renderer) + return false; + // Widgets are the replaced elements that we represent to AX as attachments + bool isWidget = renderer->isWidget(); + ASSERT(!isWidget || (renderer->isReplaced() && !isImage())); + return isWidget && ariaRoleAttribute() == UnknownRole; +} + +bool AccessibilityRenderObject::isPasswordField() const +{ + ASSERT(m_renderer); + if (!m_renderer->node() || !m_renderer->node()->isHTMLElement()) + return false; + if (ariaRoleAttribute() != UnknownRole) + return false; + + InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); + if (!inputElement) + return false; + + return inputElement->isPasswordField(); +} + +bool AccessibilityRenderObject::isFileUploadButton() const +{ + if (m_renderer && m_renderer->node() && m_renderer->node()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node()); + return input->isFileUpload(); + } + + return false; +} + +bool AccessibilityRenderObject::isInputImage() const +{ + Node* elementNode = node(); + if (roleValue() == ButtonRole && elementNode && elementNode->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(elementNode); + return input->isImageButton(); + } + + return false; +} + +bool AccessibilityRenderObject::isProgressIndicator() const +{ + return roleValue() == ProgressIndicatorRole; +} + +bool AccessibilityRenderObject::isSlider() const +{ + return roleValue() == SliderRole; +} + +bool AccessibilityRenderObject::isMenuRelated() const +{ + AccessibilityRole role = roleValue(); + return role == MenuRole + || role == MenuBarRole + || role == MenuButtonRole + || role == MenuItemRole; +} + +bool AccessibilityRenderObject::isMenu() const +{ + return roleValue() == MenuRole; +} + +bool AccessibilityRenderObject::isMenuBar() const +{ + return roleValue() == MenuBarRole; +} + +bool AccessibilityRenderObject::isMenuButton() const +{ + return roleValue() == MenuButtonRole; +} + +bool AccessibilityRenderObject::isMenuItem() const +{ + return roleValue() == MenuItemRole; +} + +bool AccessibilityRenderObject::isPressed() const +{ + ASSERT(m_renderer); + if (roleValue() != ButtonRole) + return false; + + Node* node = m_renderer->node(); + if (!node) + return false; + + // If this is an ARIA button, check the aria-pressed attribute rather than node()->active() + if (ariaRoleAttribute() == ButtonRole) { + if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true")) + return true; + return false; + } + + return node->active(); +} + +bool AccessibilityRenderObject::isIndeterminate() const +{ + ASSERT(m_renderer); + if (!m_renderer->node() || !m_renderer->node()->isElementNode()) + return false; + + InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); + if (!inputElement) + return false; + + return inputElement->isIndeterminate(); +} + +bool AccessibilityRenderObject::isNativeCheckboxOrRadio() const +{ + Node* elementNode = node(); + if (elementNode && elementNode->isElementNode()) { + InputElement* input = toInputElement(static_cast<Element*>(elementNode)); + if (input) + return input->isCheckbox() || input->isRadioButton(); + } + + return false; +} + +bool AccessibilityRenderObject::isChecked() const +{ + ASSERT(m_renderer); + if (!m_renderer->node() || !m_renderer->node()->isElementNode()) + return false; + + // First test for native checkedness semantics + InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); + if (inputElement) + return inputElement->isChecked(); + + // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) { + if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true")) + return true; + return false; + } + + // Otherwise it's not checked + return false; +} + +bool AccessibilityRenderObject::isHovered() const +{ + ASSERT(m_renderer); + return m_renderer->node() && m_renderer->node()->hovered(); +} + +bool AccessibilityRenderObject::isMultiSelectable() const +{ + ASSERT(m_renderer); + + const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr); + if (equalIgnoringCase(ariaMultiSelectable, "true")) + return true; + if (equalIgnoringCase(ariaMultiSelectable, "false")) + return false; + + if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isListBox()) + return false; + return m_renderer->node() && static_cast<HTMLSelectElement*>(m_renderer->node())->multiple(); +} + +bool AccessibilityRenderObject::isReadOnly() const +{ + ASSERT(m_renderer); + + if (isWebArea()) { + Document* document = m_renderer->document(); + if (!document) + return true; + + HTMLElement* body = document->body(); + if (body && body->isContentEditable()) + return false; + + Frame* frame = document->frame(); + if (!frame) + return true; + + return !frame->isContentEditable(); + } + + if (m_renderer->isBoxModelObject()) { + RenderBoxModelObject* box = toRenderBoxModelObject(m_renderer); + if (box->isTextField()) + return static_cast<HTMLInputElement*>(box->node())->readOnly(); + if (box->isTextArea()) + return static_cast<HTMLTextAreaElement*>(box->node())->readOnly(); + } + + return !m_renderer->node() || !m_renderer->node()->isContentEditable(); +} + +bool AccessibilityRenderObject::isOffScreen() const +{ + ASSERT(m_renderer); + IntRect contentRect = m_renderer->absoluteClippedOverflowRect(); + FrameView* view = m_renderer->frame()->view(); + FloatRect viewRect = view->visibleContentRect(); + viewRect.intersect(contentRect); + return viewRect.isEmpty(); +} + +int AccessibilityRenderObject::headingLevel() const +{ + // headings can be in block flow and non-block flow + Node* element = node(); + if (!element) + return 0; + + if (ariaRoleAttribute() == HeadingRole) + return getAttribute(aria_levelAttr).toInt(); + + if (element->hasTagName(h1Tag)) + return 1; + + if (element->hasTagName(h2Tag)) + return 2; + + if (element->hasTagName(h3Tag)) + return 3; + + if (element->hasTagName(h4Tag)) + return 4; + + if (element->hasTagName(h5Tag)) + return 5; + + if (element->hasTagName(h6Tag)) + return 6; + + return 0; +} + +bool AccessibilityRenderObject::isHeading() const +{ + return roleValue() == HeadingRole; +} + +bool AccessibilityRenderObject::isLink() const +{ + return roleValue() == WebCoreLinkRole; +} + +bool AccessibilityRenderObject::isControl() const +{ + if (!m_renderer) + return false; + + Node* node = m_renderer->node(); + return node && ((node->isElementNode() && static_cast<Element*>(node)->isFormControlElement()) + || AccessibilityObject::isARIAControl(ariaRoleAttribute())); +} + +bool AccessibilityRenderObject::isFieldset() const +{ + RenderBoxModelObject* renderer = renderBoxModelObject(); + if (!renderer) + return false; + return renderer->isFieldset(); +} + +bool AccessibilityRenderObject::isGroup() const +{ + return roleValue() == GroupRole; +} + +AccessibilityObject* AccessibilityRenderObject::selectedRadioButton() +{ + if (!isRadioGroup()) + return 0; + + // Find the child radio button that is selected (ie. the intValue == 1). + int count = m_children.size(); + for (int i = 0; i < count; ++i) { + AccessibilityObject* object = m_children[i].get(); + if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn) + return object; + } + 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->isChecked()) + return object; + } + return 0; +} + +Element* AccessibilityRenderObject::anchorElement() const +{ + if (!m_renderer) + return 0; + + AXObjectCache* cache = axObjectCache(); + RenderObject* currRenderer; + + // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. + for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) { + if (currRenderer->isAnonymousBlock()) { + RenderObject* continuation = toRenderBlock(currRenderer)->continuation(); + if (continuation) + return cache->getOrCreate(continuation)->anchorElement(); + } + } + + // bail if none found + if (!currRenderer) + return 0; + + // search up the DOM tree for an anchor element + // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement + Node* node = currRenderer->node(); + for ( ; node; node = node->parentNode()) { + if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) + return static_cast<Element*>(node); + } + + return 0; +} + +Element* AccessibilityRenderObject::actionElement() const +{ + if (!m_renderer) + return 0; + + Node* node = m_renderer->node(); + if (node) { + if (node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton())) + return input; + } else if (node->hasTagName(buttonTag)) + return static_cast<Element*>(node); + } + + if (isFileUploadButton()) + return static_cast<Element*>(m_renderer->node()); + + if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) + return static_cast<Element*>(m_renderer->node()); + + if (isImageButton()) + return static_cast<Element*>(m_renderer->node()); + + if (m_renderer->isBoxModelObject() && toRenderBoxModelObject(m_renderer)->isMenuList()) + return static_cast<Element*>(m_renderer->node()); + + AccessibilityRole role = roleValue(); + if (role == ButtonRole || role == PopUpButtonRole) + return static_cast<Element*>(m_renderer->node()); + + Element* elt = anchorElement(); + if (!elt) + elt = mouseButtonListener(); + return elt; +} + +Element* AccessibilityRenderObject::mouseButtonListener() const +{ + Node* node = m_renderer->node(); + if (!node) + return 0; + + // check if our parent is a mouse button listener + while (node && !node->isElementNode()) + node = node->parentNode(); + + if (!node) + return 0; + + // FIXME: Do the continuation search like anchorElement does + for (Element* element = static_cast<Element*>(node); element; element = element->parentElement()) { + if (element->getAttributeEventListener(eventNames().clickEvent) || element->getAttributeEventListener(eventNames().mousedownEvent) || element->getAttributeEventListener(eventNames().mouseupEvent)) + return element; + } + + return 0; +} + +void AccessibilityRenderObject::increment() +{ + if (roleValue() != SliderRole) + return; + + changeValueByPercent(5); +} + +void AccessibilityRenderObject::decrement() +{ + if (roleValue() != SliderRole) + return; + + changeValueByPercent(-5); +} + +static Element* siblingWithAriaRole(String role, Node* node) +{ + Node* sibling = node->parentNode()->firstChild(); + while (sibling) { + if (sibling->isElementNode()) { + const AtomicString& siblingAriaRole = static_cast<Element*>(sibling)->getAttribute(roleAttr); + if (equalIgnoringCase(siblingAriaRole, role)) + return static_cast<Element*>(sibling); + } + sibling = sibling->nextSibling(); + } + + return 0; +} + +Element* AccessibilityRenderObject::menuElementForMenuButton() const +{ + if (ariaRoleAttribute() != MenuButtonRole) + return 0; + + return siblingWithAriaRole("menu", renderer()->node()); +} + +AccessibilityObject* AccessibilityRenderObject::menuForMenuButton() const +{ + Element* menu = menuElementForMenuButton(); + if (menu && menu->renderer()) + return axObjectCache()->getOrCreate(menu->renderer()); + return 0; +} + +Element* AccessibilityRenderObject::menuItemElementForMenu() const +{ + if (ariaRoleAttribute() != MenuRole) + return 0; + + return siblingWithAriaRole("menuitem", renderer()->node()); +} + +AccessibilityObject* AccessibilityRenderObject::menuButtonForMenu() const +{ + Element* menuItem = menuItemElementForMenu(); + + if (menuItem && menuItem->renderer()) { + // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem + AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem->renderer()); + if (menuItemAX->isMenuButton()) + return menuItemAX; + } + return 0; +} + +String AccessibilityRenderObject::helpText() const +{ + if (!m_renderer) + return String(); + + const AtomicString& ariaHelp = getAttribute(aria_helpAttr); + if (!ariaHelp.isEmpty()) + return ariaHelp; + + for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { + if (curr->node() && curr->node()->isHTMLElement()) { + const AtomicString& summary = static_cast<Element*>(curr->node())->getAttribute(summaryAttr); + if (!summary.isEmpty()) + return summary; + const AtomicString& title = static_cast<Element*>(curr->node())->getAttribute(titleAttr); + if (!title.isEmpty()) + return title; + } + + // Only take help text from an ancestor element if its a group or an unknown role. If help was + // added to those kinds of elements, it is likely it was meant for a child element. + AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + if (axObj) { + AccessibilityRole role = axObj->roleValue(); + if (role != GroupRole && role != UnknownRole) + break; + } + } + + 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::textUnderElement() const +{ + if (!m_renderer) + return String(); + + if (isFileUploadButton()) + return toRenderFileUploadControl(m_renderer)->buttonValue(); + + Node* node = m_renderer->node(); + if (node) { + if (Frame* frame = node->document()->frame()) { + // catch stale WebCoreAXObject (see <rdar://problem/3960196>) + if (frame->document() != node->document()) + return String(); + return plainText(rangeOfContents(node).get(), TextIteratorIgnoresStyleVisibility); + } + } + + // Sometimes text fragments don't have Node's associated with them (like when + // CSS content is used to insert text). + if (m_renderer->isText()) { + RenderText* renderTextObject = toRenderText(m_renderer); + if (renderTextObject->isTextFragment()) + return String(static_cast<RenderTextFragment*>(m_renderer)->contentString()); + } + + // return the null string for anonymous text because it is non-trivial to get + // the actual text and, so far, that is not needed + return String(); +} + +Node* AccessibilityRenderObject::node() const +{ + return m_renderer ? m_renderer->node() : 0; +} + +AccessibilityButtonState AccessibilityRenderObject::checkboxOrRadioValue() const +{ + if (isNativeCheckboxOrRadio()) + return isChecked() ? ButtonStateOn : ButtonStateOff; + + return AccessibilityObject::checkboxOrRadioValue(); +} + +String AccessibilityRenderObject::valueDescription() const +{ + // Only sliders and progress bars support value descriptions currently. + if (!isProgressIndicator() && !isSlider()) + return String(); + + return getAttribute(aria_valuetextAttr).string(); +} + +float AccessibilityRenderObject::valueForRange() const +{ + if (!isProgressIndicator() && !isSlider() && !isScrollbar()) + return 0.0f; + + return getAttribute(aria_valuenowAttr).toFloat(); +} + +float AccessibilityRenderObject::maxValueForRange() const +{ + if (!isProgressIndicator() && !isSlider()) + return 0.0f; + + return getAttribute(aria_valuemaxAttr).toFloat(); +} + +float AccessibilityRenderObject::minValueForRange() const +{ + if (!isProgressIndicator() && !isSlider()) + return 0.0f; + + return getAttribute(aria_valueminAttr).toFloat(); +} + +String AccessibilityRenderObject::stringValue() const +{ + if (!m_renderer || isPasswordField()) + return String(); + + RenderBoxModelObject* cssBox = renderBoxModelObject(); + + if (ariaRoleAttribute() == StaticTextRole) { + String staticText = text(); + if (!staticText.length()) + staticText = textUnderElement(); + return staticText; + } + + if (m_renderer->isText()) + return textUnderElement(); + + if (cssBox && cssBox->isMenuList()) { + // RenderMenuList will go straight to the text() of its selected item. + // This has to be overriden in the case where the selected item has an aria label + SelectElement* selectNode = toSelectElement(static_cast<Element*>(m_renderer->node())); + int selectedIndex = selectNode->selectedIndex(); + const Vector<Element*> listItems = selectNode->listItems(); + + Element* selectedOption = 0; + if (selectedIndex >= 0 && selectedIndex < (int)listItems.size()) + selectedOption = listItems[selectedIndex]; + if (selectedOption) { + String overridenDescription = selectedOption->getAttribute(aria_labelAttr); + if (!overridenDescription.isNull()) + return overridenDescription; + } + + return toRenderMenuList(m_renderer)->text(); + } + + if (m_renderer->isListMarker()) + return toRenderListMarker(m_renderer)->text(); + + if (cssBox && cssBox->isRenderButton()) + return toRenderButton(m_renderer)->text(); + + if (isWebArea()) { + if (m_renderer->frame()) + return String(); + + // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here + VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0); + VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX); + if (startVisiblePosition.isNull() || endVisiblePosition.isNull()) + return String(); + + return plainText(makeRange(startVisiblePosition, endVisiblePosition).get(), TextIteratorIgnoresStyleVisibility); + } + + if (isTextControl()) + return text(); + + if (isFileUploadButton()) + return toRenderFileUploadControl(m_renderer)->fileTextValue(); + + // FIXME: We might need to implement a value here for more types + // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; + // this would require subclassing or making accessibilityAttributeNames do something other than return a + // single static array. + return String(); +} + +// This function implements the ARIA accessible name as described by the Mozilla +// ARIA Implementer's Guide. +static String accessibleNameForNode(Node* node) +{ + if (node->isTextNode()) + return static_cast<Text*>(node)->data(); + + if (node->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(node)->value(); + + if (node->isHTMLElement()) { + const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr); + if (!alt.isEmpty()) + return alt; + } + + return String(); +} + +String AccessibilityRenderObject::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 idList = getAttribute(attribute).string(); + if (idList.isEmpty()) + return; + + idList.replace('\n', ' '); + Vector<String> idVector; + idList.split(' ', idVector); + + unsigned size = idVector.size(); + for (unsigned i = 0; i < size; ++i) { + String idName = idVector[i]; + Element* idElement = document->getElementById(idName); + if (idElement) + elements.append(idElement); + } +} + +void AccessibilityRenderObject::ariaLabeledByElements(Vector<Element*>& elements) const +{ + elementsFromAttribute(elements, aria_labeledbyAttr); + if (!elements.size()) + elementsFromAttribute(elements, aria_labelledbyAttr); +} + +String AccessibilityRenderObject::ariaLabeledByAttribute() const +{ + Vector<Element*> elements; + ariaLabeledByElements(elements); + + return accessibilityDescriptionForElements(elements); +} + +static HTMLLabelElement* labelForElement(Element* element) +{ + RefPtr<NodeList> list = element->document()->getElementsByTagName("label"); + unsigned len = list->length(); + for (unsigned i = 0; i < len; i++) { + if (list->item(i)->hasTagName(labelTag)) { + HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i)); + if (label->control() == element) + return label; + } + } + + return 0; +} + +HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const +{ + if (!m_renderer) + return 0; + + // the control element should not be considered part of the label + if (isControl()) + return 0; + + // find if this has a parent that is a label + for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) { + if (parentNode->hasTagName(labelTag)) + return static_cast<HTMLLabelElement*>(parentNode); + } + + return 0; +} + +String AccessibilityRenderObject::title() const +{ + AccessibilityRole ariaRole = ariaRoleAttribute(); + + if (!m_renderer) + return String(); + + Node* node = m_renderer->node(); + if (!node) + return String(); + + String ariaLabel = ariaLabeledByAttribute(); + if (!ariaLabel.isEmpty()) + return ariaLabel; + + const AtomicString& title = getAttribute(titleAttr); + if (!title.isEmpty()) + return title; + + bool isInputTag = node->hasTagName(inputTag); + if (isInputTag) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (input->isTextButton()) + return input->value(); + } + + if (isInputTag || AccessibilityObject::isARIAInput(ariaRole) || isControl()) { + HTMLLabelElement* label = labelForElement(static_cast<Element*>(node)); + if (label && !titleUIElement()) + return label->innerText(); + } + + if (roleValue() == ButtonRole + || ariaRole == ListBoxOptionRole + || ariaRole == MenuItemRole + || ariaRole == MenuButtonRole + || ariaRole == RadioButtonRole + || ariaRole == CheckBoxRole + || ariaRole == TabRole + || ariaRole == PopUpButtonRole + || isHeading() + || isLink()) + return textUnderElement(); + + return String(); +} + +String AccessibilityRenderObject::ariaDescribedByAttribute() const +{ + Vector<Element*> elements; + elementsFromAttribute(elements, aria_describedbyAttr); + + return accessibilityDescriptionForElements(elements); +} + +String AccessibilityRenderObject::ariaAccessibilityDescription() const +{ + const AtomicString& ariaLabel = getAttribute(aria_labelAttr); + if (!ariaLabel.isEmpty()) + return ariaLabel; + + String ariaDescription = ariaDescribedByAttribute(); + if (!ariaDescription.isEmpty()) + return ariaDescription; + + return String(); +} + +String AccessibilityRenderObject::accessibilityDescription() const +{ + if (!m_renderer) + return String(); + + // Static text should not have a description, it should only have a stringValue. + if (roleValue() == StaticTextRole) + return String(); + + String ariaDescription = ariaAccessibilityDescription(); + if (!ariaDescription.isEmpty()) + return ariaDescription; + + if (isImage() || isInputImage() || isNativeImage()) { + Node* node = m_renderer->node(); + if (node && node->isHTMLElement()) { + const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr); + if (alt.isEmpty()) + return String(); + return alt; + } + } + + if (isWebArea()) { + Document* document = m_renderer->document(); + + // Check if the HTML element has an aria-label for the webpage. + Element* documentElement = document->documentElement(); + if (documentElement) { + const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr); + if (!ariaLabel.isEmpty()) + return ariaLabel; + } + + Node* owner = document->ownerElement(); + if (owner) { + if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { + const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr); + if (!title.isEmpty()) + return title; + return static_cast<HTMLFrameElementBase*>(owner)->getAttribute(nameAttr); + } + if (owner->isHTMLElement()) + return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); + } + owner = document->body(); + if (owner && owner->isHTMLElement()) + return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); + } + + return String(); +} + +IntRect AccessibilityRenderObject::boundingBoxRect() const +{ + RenderObject* obj = m_renderer; + + if (!obj) + return IntRect(); + + if (obj->node()) // If we are a continuation, we want to make sure to use the primary renderer. + obj = obj->node()->renderer(); + + // absoluteFocusRingQuads will query the hierarchy below this element, which for large webpages can be very slow. + // For a web area, which will have the most elements of any element, absoluteQuads should be used. + Vector<FloatQuad> quads; + if (obj->isText()) + toRenderText(obj)->absoluteQuads(quads, RenderText::ClipToEllipsis); + else if (isWebArea()) + obj->absoluteQuads(quads); + else + obj->absoluteFocusRingQuads(quads); + const size_t n = quads.size(); + if (!n) + return IntRect(); + + IntRect result; + for (size_t i = 0; i < n; ++i) { + IntRect r = quads[i].enclosingBoundingBox(); + if (!r.isEmpty()) { + if (obj->style()->hasAppearance()) + obj->theme()->adjustRepaintRect(obj, r); + result.unite(r); + } + } + + // The size of the web area should be the content size, not the clipped size. + if (isWebArea() && obj->frame()->view()) + result.setSize(obj->frame()->view()->contentsSize()); + + return result; +} + +IntRect AccessibilityRenderObject::checkboxOrRadioRect() const +{ + if (!m_renderer) + return IntRect(); + + HTMLLabelElement* label = labelForElement(static_cast<Element*>(m_renderer->node())); + if (!label || !label->renderer()) + return boundingBoxRect(); + + IntRect labelRect = axObjectCache()->getOrCreate(label->renderer())->elementRect(); + labelRect.unite(boundingBoxRect()); + return labelRect; +} + +IntRect AccessibilityRenderObject::elementRect() const +{ + // a checkbox or radio button should encompass its label + if (isCheckboxOrRadio()) + return checkboxOrRadioRect(); + + return boundingBoxRect(); +} + +IntSize AccessibilityRenderObject::size() const +{ + IntRect rect = elementRect(); + return rect.size(); +} + +IntPoint AccessibilityRenderObject::clickPoint() const +{ + // use the default position unless this is an editable web area, in which case we use the selection bounds. + if (!isWebArea() || isReadOnly()) + return AccessibilityObject::clickPoint(); + + VisibleSelection visSelection = selection(); + VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd()); + IntRect bounds = boundsForVisiblePositionRange(range); +#if PLATFORM(MAC) + bounds.setLocation(m_renderer->document()->view()->screenToContents(bounds.location())); +#endif + return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2)); +} + +AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const +{ + Element* element = anchorElement(); + if (!element) + return 0; + + // Right now, we do not support ARIA links as internal link elements + if (!element->hasTagName(aTag)) + return 0; + HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(element); + + KURL linkURL = anchor->href(); + String fragmentIdentifier = linkURL.fragmentIdentifier(); + if (fragmentIdentifier.isEmpty()) + return 0; + + // check if URL is the same as current URL + KURL documentURL = m_renderer->document()->url(); + if (!equalIgnoringFragmentIdentifier(documentURL, linkURL)) + return 0; + + Node* linkedNode = m_renderer->document()->findAnchor(fragmentIdentifier); + if (!linkedNode) + return 0; + + // The element we find may not be accessible, so find the first accessible object. + return firstAccessibleObjectFromNode(linkedNode); +} + +ESpeak AccessibilityRenderObject::speakProperty() const +{ + if (!m_renderer) + return AccessibilityObject::speakProperty(); + + return m_renderer->style()->speak(); +} + +void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const +{ + if (!m_renderer || roleValue() != RadioButtonRole) + return; + + Node* node = m_renderer->node(); + if (!node || !node->hasTagName(inputTag)) + return; + + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + // if there's a form, then this is easy + if (input->form()) { + Vector<RefPtr<Node> > formElements; + input->form()->getNamedElements(input->name(), formElements); + + unsigned len = formElements.size(); + for (unsigned i = 0; i < len; ++i) { + Node* associateElement = formElements[i].get(); + if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement->renderer())) + linkedUIElements.append(object); + } + } else { + RefPtr<NodeList> list = node->document()->getElementsByTagName("input"); + unsigned len = list->length(); + for (unsigned i = 0; i < len; ++i) { + if (list->item(i)->hasTagName(inputTag)) { + HTMLInputElement* associateElement = static_cast<HTMLInputElement*>(list->item(i)); + if (associateElement->isRadioButton() && associateElement->name() == input->name()) { + if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement->renderer())) + linkedUIElements.append(object); + } + } + } + } +} + +// linked ui elements could be all the related radio buttons in a group +// or an internal anchor connection +void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const +{ + ariaFlowToElements(linkedUIElements); + + if (isAnchor()) { + AccessibilityObject* linkedAXElement = internalLinkElement(); + if (linkedAXElement) + linkedUIElements.append(linkedAXElement); + } + + if (roleValue() == RadioButtonRole) + 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).isEmpty()) + return true; + + return false; +} + +bool AccessibilityRenderObject::ariaHasPopup() const +{ + return elementAttributeValue(aria_haspopupAttr); +} + +bool AccessibilityRenderObject::supportsARIAFlowTo() const +{ + return !getAttribute(aria_flowtoAttr).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 +{ + const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); + return !dropEffect.isEmpty(); +} + +bool AccessibilityRenderObject::supportsARIADragging() const +{ + const AtomicString& grabbed = getAttribute(aria_grabbedAttr); + 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) +{ + const AtomicString& dropEffects = getAttribute(aria_dropeffectAttr); + if (dropEffects.isEmpty()) { + effects.clear(); + return; + } + + String dropEffectsString = dropEffects.string(); + dropEffectsString.replace('\n', ' '); + dropEffectsString.split(' ', effects); +} + +bool AccessibilityRenderObject::exposesTitleUIElement() const +{ + if (!isControl()) + return false; + + // checkbox or radio buttons don't expose the title ui element unless it has a title already + if (isCheckboxOrRadio() && getAttribute(titleAttr).isEmpty()) + return false; + + if (hasTextAlternative()) + return false; + + return true; +} + +AccessibilityObject* AccessibilityRenderObject::titleUIElement() const +{ + if (!m_renderer) + return 0; + + // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset + if (isFieldset()) + return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend()); + + if (!exposesTitleUIElement()) + return 0; + + Node* element = m_renderer->node(); + HTMLLabelElement* label = labelForElement(static_cast<Element*>(element)); + if (label && label->renderer()) + return axObjectCache()->getOrCreate(label->renderer()); + + return 0; +} + +bool AccessibilityRenderObject::ariaIsHidden() const +{ + if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "true")) + return true; + + // aria-hidden hides this object and any children + AccessibilityObject* object = parentObject(); + while (object) { + if (object->isAccessibilityRenderObject() && equalIgnoringCase(static_cast<AccessibilityRenderObject*>(object)->getAttribute(aria_hiddenAttr), "true")) + return true; + object = object->parentObject(); + } + + return false; +} + +bool AccessibilityRenderObject::isDescendantOfBarrenParent() const +{ + for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { + if (!object->canHaveChildren()) + return true; + } + + return false; +} + +bool AccessibilityRenderObject::isAllowedChildOfTree() 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->parentObject(); + } + + // 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 false; + } + return true; +} + +AccessibilityObjectInclusion AccessibilityRenderObject::accessibilityIsIgnoredBase() const +{ + // The following cases can apply to any element that's a subclass of AccessibilityRenderObject. + + // Ignore invisible elements. + if (!m_renderer || m_renderer->style()->visibility() != VISIBLE) + return IgnoreObject; + + // Anything marked as aria-hidden or a child of something aria-hidden must be hidden. + if (ariaIsHidden()) + return IgnoreObject; + + // Anything that is a presentational role must be hidden. + if (isPresentationalChildOfAriaRole()) + return IgnoreObject; + + // Allow the platform to make a decision. + AccessibilityObjectInclusion decision = accessibilityPlatformIncludesObject(); + if (decision == IncludeObject) + return IncludeObject; + if (decision == IgnoreObject) + return IgnoreObject; + + return DefaultBehavior; +} + +bool AccessibilityRenderObject::accessibilityIsIgnored() const +{ + // Check first if any of the common reasons cause this element to be ignored. + // Then process other use cases that need to be applied to all the various roles + // that AccessibilityRenderObjects take on. + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + 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; + + if (roleValue() == PresentationalRole || inheritsPresentationalRole()) + return true; + + // An ARIA tree can only have tree items and static text as children. + if (!isAllowedChildOfTree()) + return true; + + // ignore popup menu items because AppKit does + for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { + if (parent->isBoxModelObject() && toRenderBoxModelObject(parent)->isMenuList()) + return true; + } + + // 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() && controlObject->isCheckboxOrRadio()) + return true; + + // 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) + return true; + RenderText* renderText = toRenderText(m_renderer); + if (m_renderer->isBR() || !renderText->firstTextBox()) + return true; + + // static text beneath TextControls is reported along with the text control text so it's ignored. + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + if (parent->roleValue() == TextFieldRole) + return true; + } + + // text elements that are just empty whitespace should not be returned + return renderText->text()->containsOnlyWhitespace(); + } + + if (isHeading()) + return false; + + if (isLink()) + return false; + + // all controls are accessible + if (isControl()) + return false; + + if (ariaRoleAttribute() != UnknownRole) + return false; + + if (!helpText().isEmpty()) + return false; + + // don't ignore labels, because they serve as TitleUIElements + Node* node = m_renderer->node(); + if (node && node->hasTagName(labelTag)) + return false; + + // Anything that is content editable should not be ignored. + // However, one cannot just call node->isContentEditable() since that will ask if its parents + // are also editable. Only the top level content editable region should be exposed. + if (node && node->isElementNode()) { + Element* element = static_cast<Element*>(node); + const AtomicString& contentEditable = element->getAttribute(contenteditableAttr); + if (equalIgnoringCase(contentEditable, "true")) + return false; + } + + // if this element has aria attributes on it, it should not be ignored. + if (supportsARIAAttributes()) + return false; + + if (m_renderer->isBlockFlow() && m_renderer->childrenInline()) + return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener(); + + // ignore images seemingly used as spacers + if (isImage()) { + if (node && node->isElementNode()) { + Element* elt = static_cast<Element*>(node); + const AtomicString& alt = elt->getAttribute(altAttr); + // don't ignore an image that has an alt tag + if (!alt.isEmpty()) + return false; + // informal standard is to ignore images with zero-length alt strings + if (!alt.isNull()) + return true; + } + + if (node && node->hasTagName(canvasTag)) { + RenderHTMLCanvas* canvas = toRenderHTMLCanvas(m_renderer); + if (canvas->height() <= 1 || canvas->width() <= 1) + return true; + return false; + } + + if (isNativeImage()) { + // check for one-dimensional image + RenderImage* image = toRenderImage(m_renderer); + if (image->height() <= 1 || image->width() <= 1) + return true; + + // check whether rendered image was stretched from one-dimensional file image + if (image->cachedImage()) { + IntSize imageSize = image->cachedImage()->imageSize(image->view()->zoomFactor()); + return imageSize.height() <= 1 || imageSize.width() <= 1; + } + } + return false; + } + + // make a platform-specific decision + if (isAttachment()) + return accessibilityIgnoreAttachment(); + + return !m_renderer->isListMarker() && !isWebArea(); +} + +bool AccessibilityRenderObject::isLoaded() const +{ + return !m_renderer->document()->parser(); +} + +double AccessibilityRenderObject::estimatedLoadingProgress() const +{ + if (!m_renderer) + return 0; + + if (isLoaded()) + return 1.0; + + Page* page = m_renderer->document()->page(); + if (!page) + return 0; + + return page->progress()->estimatedProgress(); +} + +int AccessibilityRenderObject::layoutCount() const +{ + if (!m_renderer->isRenderView()) + return 0; + return toRenderView(m_renderer)->frameView()->layoutCount(); +} + +String AccessibilityRenderObject::text() const +{ + // If this is a user defined static text, use the accessible name computation. + if (ariaRoleAttribute() == StaticTextRole) + return ariaAccessibilityDescription(); + + if (!isTextControl() || isPasswordField()) + return String(); + + if (isNativeTextControl()) + return toRenderTextControl(m_renderer)->text(); + + Node* node = m_renderer->node(); + if (!node) + return String(); + if (!node->isElementNode()) + return String(); + + return static_cast<Element*>(node)->innerText(); +} + +int AccessibilityRenderObject::textLength() const +{ + ASSERT(isTextControl()); + + if (isPasswordField()) + return -1; // need to return something distinct from 0 + + return text().length(); +} + +PlainTextRange AccessibilityRenderObject::ariaSelectedTextRange() const +{ + Node* node = m_renderer->node(); + if (!node) + return PlainTextRange(); + + ExceptionCode ec = 0; + VisibleSelection visibleSelection = selection(); + RefPtr<Range> currentSelectionRange = visibleSelection.toNormalizedRange(); + if (!currentSelectionRange || !currentSelectionRange->intersectsNode(node, ec)) + return PlainTextRange(); + + int start = indexForVisiblePosition(visibleSelection.start()); + int end = indexForVisiblePosition(visibleSelection.end()); + + return PlainTextRange(start, end - start); +} + +String AccessibilityRenderObject::selectedText() const +{ + ASSERT(isTextControl()); + + if (isPasswordField()) + return String(); // need to return something distinct from empty string + + if (isNativeTextControl()) { + RenderTextControl* textControl = toRenderTextControl(m_renderer); + return textControl->text().substring(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); + } + + if (ariaRoleAttribute() == UnknownRole) + return String(); + + return doAXStringForRange(ariaSelectedTextRange()); +} + +const AtomicString& AccessibilityRenderObject::accessKey() const +{ + Node* node = m_renderer->node(); + if (!node) + return nullAtom; + if (!node->isElementNode()) + return nullAtom; + return static_cast<Element*>(node)->getAttribute(accesskeyAttr); +} + +VisibleSelection AccessibilityRenderObject::selection() const +{ + return m_renderer->frame()->selection()->selection(); +} + +PlainTextRange AccessibilityRenderObject::selectedTextRange() const +{ + ASSERT(isTextControl()); + + if (isPasswordField()) + return PlainTextRange(); + + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (isNativeTextControl() && ariaRole == UnknownRole) { + RenderTextControl* textControl = toRenderTextControl(m_renderer); + return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); + } + + if (ariaRole == UnknownRole) + return PlainTextRange(); + + return ariaSelectedTextRange(); +} + +void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) +{ + if (isNativeTextControl()) { + setSelectionRange(m_renderer->node(), range.start, range.start + range.length); + return; + } + + Document* document = m_renderer->document(); + if (!document) + return; + Frame* frame = document->frame(); + if (!frame) + return; + Node* node = m_renderer->node(); + frame->selection()->setSelection(VisibleSelection(Position(node, range.start), + Position(node, range.start + range.length), DOWNSTREAM)); +} + +KURL AccessibilityRenderObject::url() const +{ + if (isAnchor() && m_renderer->node()->hasTagName(aTag)) { + if (HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(anchorElement())) + return anchor->href(); + } + + if (isWebArea()) + return m_renderer->document()->url(); + + if (isImage() && m_renderer->node() && m_renderer->node()->hasTagName(imgTag)) + return static_cast<HTMLImageElement*>(m_renderer->node())->src(); + + if (isInputImage()) + return static_cast<HTMLInputElement*>(m_renderer->node())->src(); + + return KURL(); +} + +bool AccessibilityRenderObject::isVisited() const +{ + // FIXME: Is it a privacy violation to expose visited information to accessibility APIs? + return m_renderer->style()->isLink() && m_renderer->style()->insideLink() == InsideVisitedLink; +} + +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) const +{ + if (!m_renderer) + return false; + + return equalIgnoringCase(getAttribute(attributeName), "true"); +} + +void AccessibilityRenderObject::setIsExpanded(bool isExpanded) +{ + // Combo boxes, tree items and rows can be expanded (in different ways on different platforms). + // That action translates into setting the aria-expanded attribute to true. + AccessibilityRole role = roleValue(); + switch (role) { + case ComboBoxRole: + case TreeItemRole: + case RowRole: + setElementAttributeValue(aria_expandedAttr, isExpanded); + break; + default: + break; + } +} + +bool AccessibilityRenderObject::isRequired() const +{ + if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true")) + return true; + + Node* n = node(); + if (n && (n->isElementNode() && static_cast<Element*>(n)->isFormControlElement())) + return static_cast<HTMLFormControlElement*>(n)->required(); + + return false; +} + +bool AccessibilityRenderObject::isSelected() const +{ + if (!m_renderer) + return false; + + Node* node = m_renderer->node(); + if (!node) + return false; + + const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); + 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) + return false; + + Document* document = m_renderer->document(); + if (!document) + return false; + + Node* focusedNode = document->focusedNode(); + if (!focusedNode) + return false; + + // 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())) + return true; + + return false; +} + +void AccessibilityRenderObject::setFocused(bool on) +{ + if (!canSetFocusAttribute()) + return; + + if (!on) + m_renderer->document()->setFocusedNode(0); + else { + if (m_renderer->node()->isElementNode()) + static_cast<Element*>(m_renderer->node())->focus(); + else + m_renderer->document()->setFocusedNode(m_renderer->node()); + } +} + +void AccessibilityRenderObject::changeValueByPercent(float percentChange) +{ + float range = maxValueForRange() - minValueForRange(); + float value = valueForRange(); + + value += range * (percentChange / 100); + setValue(String::number(value)); + + axObjectCache()->postNotification(m_renderer, AXObjectCache::AXValueChanged, true); +} + +void AccessibilityRenderObject::setSelected(bool enabled) +{ + setElementAttributeValue(aria_selectedAttr, enabled); +} + +void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) +{ + // Setting selected only makes sense in trees and tables (and tree-tables). + AccessibilityRole role = roleValue(); + if (role != TreeRole && role != TreeGridRole && role != TableRole) + return; + + bool isMulti = isMultiSelectable(); + unsigned count = selectedRows.size(); + if (count > 1 && !isMulti) + count = 1; + + for (unsigned k = 0; k < count; ++k) + selectedRows[k]->setSelected(true); +} + +void AccessibilityRenderObject::setValue(const String& string) +{ + if (!m_renderer || !m_renderer->node() || !m_renderer->node()->isElementNode()) + return; + Element* element = static_cast<Element*>(m_renderer->node()); + + if (roleValue() == SliderRole) + element->setAttribute(aria_valuenowAttr, string); + + if (!m_renderer->isBoxModelObject()) + return; + RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); + + // FIXME: Do we want to do anything here for ARIA textboxes? + if (renderer->isTextField()) { + // FIXME: This is not safe! Other elements could have a TextField renderer. + static_cast<HTMLInputElement*>(element)->setValue(string); + } else if (renderer->isTextArea()) { + // FIXME: This is not safe! Other elements could have a TextArea renderer. + static_cast<HTMLTextAreaElement*>(element)->setValue(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); + + return !ariaOwns.isEmpty(); +} + +bool AccessibilityRenderObject::isEnabled() const +{ + ASSERT(m_renderer); + + if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true")) + return false; + + Node* node = m_renderer->node(); + if (!node || !node->isElementNode()) + return true; + + return static_cast<Element*>(node)->isEnabledFormControl(); +} + +RenderView* AccessibilityRenderObject::topRenderer() const +{ + return m_renderer->document()->topDocument()->renderView(); +} + +Document* AccessibilityRenderObject::document() const +{ + if (!m_renderer) + return 0; + return m_renderer->document(); +} + +FrameView* AccessibilityRenderObject::topDocumentFrameView() const +{ + return topRenderer()->view()->frameView(); +} + +Widget* AccessibilityRenderObject::widget() const +{ + if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isWidget()) + return 0; + return toRenderWidget(m_renderer)->widget(); +} + +AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const +{ + // find an image that is using this map + if (!map) + return 0; + + HTMLImageElement* imageElement = map->imageElement(); + if (!imageElement) + return 0; + + return axObjectCache()->getOrCreate(imageElement->renderer()); +} + +void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) +{ + Document* document = m_renderer->document(); + RefPtr<HTMLCollection> coll = document->links(); + Node* curr = coll->firstItem(); + while (curr) { + RenderObject* obj = curr->renderer(); + if (obj) { + RefPtr<AccessibilityObject> axobj = document->axObjectCache()->getOrCreate(obj); + ASSERT(axobj); + if (!axobj->accessibilityIsIgnored() && axobj->isLink()) + result.append(axobj); + } else { + Node* parent = curr->parentNode(); + if (parent && curr->hasTagName(areaTag) && parent->hasTagName(mapTag)) { + AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole)); + areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(curr)); + areaObject->setHTMLMapElement(static_cast<HTMLMapElement*>(parent)); + areaObject->setParent(accessibilityParentForImageMap(static_cast<HTMLMapElement*>(parent))); + + result.append(areaObject); + } + } + curr = coll->nextItem(); + } +} + +FrameView* AccessibilityRenderObject::documentFrameView() const +{ + if (!m_renderer || !m_renderer->document()) + return 0; + + // this is the RenderObject's Document's Frame's FrameView + return m_renderer->document()->view(); +} + +Widget* AccessibilityRenderObject::widgetForAttachmentView() const +{ + if (!isAttachment()) + return 0; + return toRenderWidget(m_renderer)->widget(); +} + +FrameView* AccessibilityRenderObject::frameViewIfRenderView() const +{ + if (!m_renderer->isRenderView()) + return 0; + // this is the RenderObject's Document's renderer's FrameView + return m_renderer->view()->frameView(); +} + +// This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns +// a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file +VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const +{ + if (!m_renderer) + return VisiblePositionRange(); + + // construct VisiblePositions for start and end + Node* node = m_renderer->node(); + if (!node) + return VisiblePositionRange(); + + VisiblePosition startPos = firstDeepEditingPositionForNode(node); + VisiblePosition endPos = lastDeepEditingPositionForNode(node); + + // the VisiblePositions are equal for nodes like buttons, so adjust for that + // FIXME: Really? [button, 0] and [button, 1] are distinct (before and after the button) + // I expect this code is only hit for things like empty divs? In which case I don't think + // the behavior is correct here -- eseidel + if (startPos == endPos) { + endPos = endPos.next(); + if (endPos.isNull()) + endPos = startPos; + } + + return VisiblePositionRange(startPos, endPos); +} + +VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const +{ + if (!lineCount || !m_renderer) + return VisiblePositionRange(); + + // iterate over the lines + // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the + // last offset of the last line + VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForCoordinates(0, 0); + VisiblePosition savedVisiblePos; + while (--lineCount) { + savedVisiblePos = visiblePos; + visiblePos = nextLinePosition(visiblePos, 0); + if (visiblePos.isNull() || visiblePos == savedVisiblePos) + return VisiblePositionRange(); + } + + // make a caret selection for the marker position, then extend it to the line + // NOTE: ignores results of sel.modify because it returns false when + // starting at an empty line. The resulting selection in that case + // will be a caret at visiblePos. + SelectionController selection; + selection.setSelection(VisibleSelection(visiblePos)); + selection.modify(SelectionController::AlterationExtend, DirectionRight, LineBoundary); + + return VisiblePositionRange(selection.selection().visibleStart(), selection.selection().visibleEnd()); +} + +VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const +{ + if (!m_renderer) + return VisiblePosition(); + + if (isNativeTextControl()) + return toRenderTextControl(m_renderer)->visiblePositionForIndex(index); + + if (!allowsTextRanges() && !m_renderer->isText()) + return VisiblePosition(); + + Node* node = m_renderer->node(); + if (!node) + return VisiblePosition(); + + if (index <= 0) + return VisiblePosition(node, 0, DOWNSTREAM); + + ExceptionCode ec = 0; + RefPtr<Range> range = Range::create(m_renderer->document()); + range->selectNodeContents(node, ec); + CharacterIterator it(range.get()); + it.advance(index - 1); + return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM); +} + +int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const +{ + if (isNativeTextControl()) + return toRenderTextControl(m_renderer)->indexForVisiblePosition(pos); + + if (!isTextControl()) + return 0; + + Node* node = m_renderer->node(); + if (!node) + return 0; + + Position indexPosition = pos.deepEquivalent(); + if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != node) + return 0; + + ExceptionCode ec = 0; + RefPtr<Range> range = Range::create(m_renderer->document()); + range->setStart(node, 0, ec); + range->setEnd(indexPosition.node(), indexPosition.deprecatedEditingOffset(), ec); + return TextIterator::rangeLength(range.get()); +} + +IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const +{ + if (visiblePositionRange.isNull()) + return IntRect(); + + // Create a mutable VisiblePositionRange. + VisiblePositionRange range(visiblePositionRange); + IntRect rect1 = range.start.absoluteCaretBounds(); + IntRect rect2 = range.end.absoluteCaretBounds(); + + // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds + if (rect2.y() != rect1.y()) { + VisiblePosition endOfFirstLine = endOfLine(range.start); + if (range.start == endOfFirstLine) { + range.start.setAffinity(DOWNSTREAM); + rect1 = range.start.absoluteCaretBounds(); + } + if (range.end == endOfFirstLine) { + range.end.setAffinity(UPSTREAM); + rect2 = range.end.absoluteCaretBounds(); + } + } + + IntRect ourrect = rect1; + ourrect.unite(rect2); + + // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead + if (rect1.bottom() != rect2.bottom()) { + RefPtr<Range> dataRange = makeRange(range.start, range.end); + IntRect boundingBox = dataRange->boundingBox(); + String rangeString = plainText(dataRange.get()); + if (rangeString.length() > 1 && !boundingBox.isEmpty()) + ourrect = boundingBox; + } + +#if PLATFORM(MAC) + return m_renderer->document()->view()->contentsToScreen(ourrect); +#else + return ourrect; +#endif +} + +void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const +{ + if (range.start.isNull() || range.end.isNull()) + 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) + m_renderer->frame()->selection()->moveTo(range.start, true); + else { + VisibleSelection newSelection = VisibleSelection(range.start, range.end); + m_renderer->frame()->selection()->setSelection(newSelection); + } +} + +VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const +{ + if (!m_renderer) + return VisiblePosition(); + + // convert absolute point to view coordinates + FrameView* frameView = m_renderer->document()->topDocument()->renderer()->view()->frameView(); + RenderView* renderView = topRenderer(); + Node* innerNode = 0; + + // locate the node containing the point + IntPoint pointResult; + while (1) { + IntPoint ourpoint; +#if PLATFORM(MAC) + ourpoint = frameView->screenToContents(point); +#else + ourpoint = point; +#endif + HitTestRequest request(HitTestRequest::ReadOnly | + HitTestRequest::Active); + HitTestResult result(ourpoint); + renderView->layer()->hitTest(request, result); + innerNode = result.innerNode(); + if (!innerNode) + return VisiblePosition(); + + RenderObject* renderer = innerNode->renderer(); + if (!renderer) + return VisiblePosition(); + + pointResult = result.localPoint(); + + // done if hit something other than a widget + if (!renderer->isWidget()) + break; + + // descend into widget (FRAME, IFRAME, OBJECT...) + Widget* widget = toRenderWidget(renderer)->widget(); + if (!widget || !widget->isFrameView()) + break; + Frame* frame = static_cast<FrameView*>(widget)->frame(); + if (!frame) + break; + renderView = frame->document()->renderView(); + frameView = static_cast<FrameView*>(widget); + } + + return innerNode->renderer()->positionForPoint(pointResult); +} + +// NOTE: Consider providing this utility method as AX API +VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const +{ + if (!isTextControl()) + return VisiblePosition(); + + // lastIndexOK specifies whether the position after the last character is acceptable + if (indexValue >= text().length()) { + if (!lastIndexOK || indexValue > text().length()) + return VisiblePosition(); + } + VisiblePosition position = visiblePositionForIndex(indexValue); + position.setAffinity(DOWNSTREAM); + return position; +} + +// NOTE: Consider providing this utility method as AX API +int AccessibilityRenderObject::index(const VisiblePosition& position) const +{ + if (!isTextControl()) + return -1; + + Node* node = position.deepEquivalent().node(); + if (!node) + return -1; + + for (RenderObject* renderer = node->renderer(); renderer && renderer->node(); renderer = renderer->parent()) { + if (renderer == m_renderer) + return indexForVisiblePosition(position); + } + + return -1; +} + +// Given a line number, the range of characters of the text associated with this accessibility +// object that contains the line number. +PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const +{ + if (!isTextControl()) + return PlainTextRange(); + + // iterate to the specified line + VisiblePosition visiblePos = visiblePositionForIndex(0); + VisiblePosition savedVisiblePos; + for (unsigned lineCount = lineNumber; lineCount; lineCount -= 1) { + savedVisiblePos = visiblePos; + visiblePos = nextLinePosition(visiblePos, 0); + if (visiblePos.isNull() || visiblePos == savedVisiblePos) + return PlainTextRange(); + } + + // Get the end of the line based on the starting position. + VisiblePosition endPosition = endOfLine(visiblePos); + + int index1 = indexForVisiblePosition(visiblePos); + int index2 = indexForVisiblePosition(endPosition); + + // add one to the end index for a line break not caused by soft line wrap (to match AppKit) + if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull()) + index2 += 1; + + // return nil rather than an zero-length range (to match AppKit) + if (index1 == index2) + return PlainTextRange(); + + return PlainTextRange(index1, index2 - index1); +} + +// The composed character range in the text associated with this accessibility object that +// is specified by the given index value. This parameterized attribute returns the complete +// range of characters (including surrogate pairs of multi-byte glyphs) at the given index. +PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const +{ + if (!isTextControl()) + return PlainTextRange(); + + String elementText = text(); + if (!elementText.length() || index > elementText.length() - 1) + return PlainTextRange(); + + return PlainTextRange(index, 1); +} + +// A substring of the text associated with this accessibility object that is +// specified by the given character range. +String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const +{ + if (isPasswordField()) + return String(); + + if (!range.length) + return String(); + + if (!isTextControl()) + return String(); + + String elementText = text(); + if (range.start + range.length > elementText.length()) + return String(); + + return elementText.substring(range.start, range.length); +} + +// The bounding rectangle of the text associated with this accessibility object that is +// specified by the given range. This is the bounding rectangle a sighted user would see +// on the display screen, in pixels. +IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const +{ + if (allowsTextRanges()) + return boundsForVisiblePositionRange(visiblePositionRangeForRange(range)); + return IntRect(); +} + +AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const +{ + if (!area) + return 0; + + HTMLMapElement* map = static_cast<HTMLMapElement*>(area->parentNode()); + AccessibilityObject* parent = accessibilityParentForImageMap(map); + if (!parent) + return 0; + + AccessibilityObject::AccessibilityChildrenVector children = parent->children(); + + unsigned count = children.size(); + for (unsigned k = 0; k < count; ++k) { + if (children[k]->elementRect().contains(point)) + return children[k].get(); + } + + return 0; +} + +AccessibilityObject* AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const +{ + if (!m_renderer || !m_renderer->hasLayer()) + return 0; + + RenderLayer* layer = toRenderBox(m_renderer)->layer(); + + HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); + HitTestResult hitTestResult = HitTestResult(point); + layer->hitTest(request, hitTestResult); + if (!hitTestResult.innerNode()) + return 0; + Node* node = hitTestResult.innerNode()->shadowAncestorNode(); + + if (node->hasTagName(areaTag)) + return accessibilityImageMapHitTest(static_cast<HTMLAreaElement*>(node), point); + + if (node->hasTagName(optionTag)) + node = static_cast<HTMLOptionElement*>(node)->ownerSelectElement(); + + RenderObject* obj = node->renderer(); + if (!obj) + return 0; + + AccessibilityObject* result = obj->document()->axObjectCache()->getOrCreate(obj); + result->updateChildrenIfNecessary(); + + // Allow the element to perform any hit-testing it might need to do to reach non-render children. + result = result->elementAccessibilityHitTest(point); + + if (result->accessibilityIsIgnored()) { + // If this element is the label of a control, a hit test should return the control. + AccessibilityObject* controlObject = result->correspondingControlForLabelElement(); + if (controlObject && !controlObject->exposesTitleUIElement()) + return controlObject; + + result = result->parentObjectUnignored(); + } + + return result; +} + +bool AccessibilityRenderObject::shouldFocusActiveDescendant() const +{ + switch (ariaRoleAttribute()) { + case GroupRole: + case ComboBoxRole: + case ListBoxRole: + case MenuRole: + case MenuBarRole: + case RadioGroupRole: + case RowRole: + case PopUpButtonRole: + 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 + status + timer + */ + return true; + default: + return false; + } +} + +AccessibilityObject* AccessibilityRenderObject::activeDescendant() const +{ + if (!m_renderer) + return 0; + + if (m_renderer->node() && !m_renderer->node()->isElementNode()) + return 0; + Element* element = static_cast<Element*>(m_renderer->node()); + + const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr); + if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) + return 0; + + Element* target = document()->getElementById(activeDescendantAttrStr); + if (!target) + return 0; + + 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; + return 0; +} + +void AccessibilityRenderObject::handleAriaExpandedChanged() +{ + // Find if a parent of this object should handle aria-expanded changes. + AccessibilityObject* containerParent = this->parentObject(); + while (containerParent) { + bool foundParent = false; + + switch (containerParent->roleValue()) { + case TreeRole: + case TreeGridRole: + case GridRole: + case TableRole: + case BrowserRole: + foundParent = true; + break; + default: + break; + } + + if (foundParent) + break; + + containerParent = containerParent->parentObject(); + } + + // Post that the row count changed. + if (containerParent) + axObjectCache()->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged, true); + + // Post that the specific row either collapsed or expanded. + if (roleValue() == RowRole || roleValue() == TreeItemRole) + axObjectCache()->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed, true); +} + +void AccessibilityRenderObject::handleActiveDescendantChanged() +{ + Element* element = static_cast<Element*>(renderer()->node()); + if (!element) + return; + Document* doc = renderer()->document(); + if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedNode() != element) + return; + AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant()); + + if (activedescendant && shouldFocusActiveDescendant()) + doc->axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged, true); +} + +AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElement() const +{ + HTMLLabelElement* labelElement = labelElementContainer(); + if (!labelElement) + return 0; + + HTMLElement* correspondingControl = labelElement->control(); + if (!correspondingControl) + return 0; + + return axObjectCache()->getOrCreate(correspondingControl->renderer()); +} + +AccessibilityObject* AccessibilityRenderObject::correspondingLabelForControlElement() const +{ + if (!m_renderer) + return 0; + + Node* node = m_renderer->node(); + if (node && node->isHTMLElement()) { + HTMLLabelElement* label = labelForElement(static_cast<Element*>(node)); + if (label) + return axObjectCache()->getOrCreate(label->renderer()); + } + + return 0; +} + +bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject* renderer) const +{ + // AX clients will listen for AXValueChange on a text control. + if (renderer->isTextControl()) + return true; + + // AX clients will listen for AXSelectedChildrenChanged on listboxes. + Node* node = renderer->node(); + if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListBox())) + return true; + + // Textboxes should send out notifications. + if (nodeHasRole(node, "textbox")) + return true; + + return false; +} + +AccessibilityObject* AccessibilityRenderObject::observableObject() const +{ + // Find the object going up the parent chain that is used in accessibility to monitor certain notifications. + for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) { + if (renderObjectIsObservable(renderer)) + return axObjectCache()->getOrCreate(renderer); + } + + return 0; +} + +AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const +{ + const AtomicString& ariaRole = getAttribute(roleAttr); + if (ariaRole.isNull() || ariaRole.isEmpty()) + return UnknownRole; + + AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole); + + if (role == ButtonRole && ariaHasPopup()) + role = PopUpButtonRole; + + if (role == TextAreaRole && ariaIsMultiline()) + role = TextFieldRole; + + 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 (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 (parentObjectUnignored()->ariaRoleAttribute() == GroupRole) + return MenuButtonRole; + if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) + return MenuItemRole; + } + + return UnknownRole; +} + +AccessibilityRole AccessibilityRenderObject::ariaRoleAttribute() const +{ + return m_ariaRole; +} + +void AccessibilityRenderObject::updateAccessibilityRole() +{ + bool ignoredStatus = accessibilityIsIgnored(); + m_role = determineAccessibilityRole(); + + // The AX hierarchy only needs to be updated if the ignored status of an element has changed. + if (ignoredStatus != accessibilityIsIgnored()) + childrenChanged(); +} + +AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() +{ + if (!m_renderer) + return UnknownRole; + + m_ariaRole = determineAriaRoleAttribute(); + + Node* node = m_renderer->node(); + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole != UnknownRole) + return ariaRole; + + RenderBoxModelObject* cssBox = renderBoxModelObject(); + + if (node && node->isLink()) { + if (cssBox && cssBox->isImage()) + return ImageMapRole; + return WebCoreLinkRole; + } + if (cssBox && cssBox->isListItem()) + return ListItemRole; + if (m_renderer->isListMarker()) + return ListMarkerRole; + if (node && node->hasTagName(buttonTag)) + return ButtonRole; + if (m_renderer->isText()) + return StaticTextRole; + if (cssBox && cssBox->isImage()) { + if (node && node->hasTagName(inputTag)) + return ButtonRole; + return ImageRole; + } + if (node && node->hasTagName(canvasTag)) + return ImageRole; + + if (cssBox && cssBox->isRenderView()) + return WebAreaRole; + + if (cssBox && cssBox->isTextField()) + return TextFieldRole; + + if (cssBox && cssBox->isTextArea()) + return TextAreaRole; + + if (node && node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (input->isCheckbox()) + return CheckBoxRole; + if (input->isRadioButton()) + return RadioButtonRole; + if (input->isTextButton()) + return ButtonRole; + } + + if (node && node->hasTagName(buttonTag)) + return ButtonRole; + + if (isFileUploadButton()) + return ButtonRole; + + if (cssBox && cssBox->isMenuList()) + return PopUpButtonRole; + + if (headingLevel()) + return HeadingRole; + + if (node && node->hasTagName(ddTag)) + return DefinitionListDefinitionRole; + + if (node && node->hasTagName(dtTag)) + return DefinitionListTermRole; + + if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) + return AnnotationRole; + +#if PLATFORM(GTK) + // Gtk ATs expect all tables, data and layout, to be exposed as tables. + if (node && (node->hasTagName(tdTag) || node->hasTagName(thTag))) + return CellRole; + + if (node && node->hasTagName(trTag)) + return RowRole; + + if (node && node->hasTagName(tableTag)) + return TableRole; +#endif + + // Table sections should be ignored. + if (m_renderer->isTableSection()) + return IgnoredRole; + +#if PLATFORM(GTK) + if (m_renderer->isHR()) + return SplitterRole; +#endif + + if (m_renderer->isBlockFlow() || (node && node->hasTagName(labelTag))) + return GroupRole; + + // If the element does not have role, but it has ARIA attributes, accessibility should fallback to exposing it as a group. + if (supportsARIAAttributes()) + return GroupRole; + + return UnknownRole; +} + +AccessibilityOrientation AccessibilityRenderObject::orientation() const +{ + const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); + if (equalIgnoringCase(ariaOrientation, "horizontal")) + return AccessibilityOrientationHorizontal; + if (equalIgnoringCase(ariaOrientation, "vertical")) + return AccessibilityOrientationVertical; + + return AccessibilityObject::orientation(); +} + +bool AccessibilityRenderObject::inheritsPresentationalRole() const +{ + // ARIA spec says that when a parent object is presentational, and it has required child elements, + // those child elements are also presentational. For example, <li> becomes presentational from <ul>. + // http://www.w3.org/WAI/PF/aria/complete#presentation + DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, listItemParents, ()); + + HashSet<QualifiedName>* possibleParentTagNames = 0; + switch (roleValue()) { + case ListItemRole: + case ListMarkerRole: + if (listItemParents.isEmpty()) { + listItemParents.add(ulTag); + listItemParents.add(olTag); + listItemParents.add(dlTag); + } + possibleParentTagNames = &listItemParents; + break; + default: + break; + } + + // Not all elements need to check for this, only ones that are required children. + if (!possibleParentTagNames) + return false; + + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + if (!parent->isAccessibilityRenderObject()) + continue; + + Node* elementNode = static_cast<AccessibilityRenderObject*>(parent)->node(); + if (!elementNode || !elementNode->isElementNode()) + continue; + + // If native tag of the parent element matches an acceptable name, then return + // based on its presentational status. + if (possibleParentTagNames->contains(static_cast<Element*>(elementNode)->tagQName())) + return parent->roleValue() == PresentationalRole; + } + + return false; +} + +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; +} + +bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const +{ + switch (m_ariaRole) { + case ButtonRole: + case SliderRole: + case ImageRole: + case ProgressIndicatorRole: + // case SeparatorRole: + return true; + default: + return false; + } +} + +bool AccessibilityRenderObject::canSetFocusAttribute() const +{ + ASSERT(m_renderer); + Node* node = m_renderer->node(); + + // NOTE: It would be more accurate to ask the document whether setFocusedNode() would + // do anything. For example, setFocusedNode() will do nothing if the current focused + // node will not relinquish the focus. + if (!node || !node->isElementNode()) + return false; + + if (!static_cast<Element*>(node)->isEnabledFormControl()) + 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 node->supportsFocus(); + } +} + +bool AccessibilityRenderObject::canSetExpandedAttribute() const +{ + // An object can be expanded if it aria-expanded is true or false. + const AtomicString& ariaExpanded = getAttribute(aria_expandedAttr); + return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false"); +} + +bool AccessibilityRenderObject::canSetValueAttribute() const +{ + if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true")) + return false; + + // Any node could be contenteditable, so isReadOnly should be relied upon + // for this information for all elements. + return isProgressIndicator() || isSlider() || !isReadOnly(); +} + +bool AccessibilityRenderObject::canSetTextRangeAttributes() const +{ + return isTextControl(); +} + +void AccessibilityRenderObject::contentChanged() +{ + // If this element supports ARIA live regions, then notify the AT of changes. + AXObjectCache* cache = axObjectCache(); + for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) { + AccessibilityObject* parent = cache->get(renderParent); + if (!parent) + continue; + + // If we find a parent that has ARIA live region on, send the notification and stop processing. + // The spec does not talk about nested live regions. + if (parent->supportsARIALiveRegion()) { + axObjectCache()->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true); + break; + } + } +} + +void AccessibilityRenderObject::childrenChanged() +{ + // This method is meant as a quick way of marking a portion of the accessibility tree dirty. + if (!m_renderer) + return; + + bool sentChildrenChanged = false; + + // Go up the accessibility parent chain, but only if the element already exists. This method is + // called during render layouts, minimal work should be done. + // If AX elements are created now, they could interrogate the render tree while it's in a funky state. + // At the same time, process ARIA live region changes. + for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) { + if (!parent->isAccessibilityRenderObject()) + continue; + + AccessibilityRenderObject* axParent = static_cast<AccessibilityRenderObject*>(parent); + + // Send the children changed notification on the first accessibility render object ancestor. + if (!sentChildrenChanged) { + axObjectCache()->postNotification(axParent->renderer(), AXObjectCache::AXChildrenChanged, true); + sentChildrenChanged = true; + } + + // Only do work if the children haven't been marked dirty. This has the effect of blocking + // future live region change notifications until the AX tree has been accessed again. This + // is a good performance win for all parties. + if (!axParent->needsToUpdateChildren()) { + axParent->setNeedsToUpdateChildren(); + + // If this element supports ARIA live regions, then notify the AT of changes. + if (axParent->supportsARIALiveRegion()) + axObjectCache()->postNotification(axParent->renderer(), AXObjectCache::AXLiveRegionChanged, true); + } + } +} + +bool AccessibilityRenderObject::canHaveChildren() const +{ + if (!m_renderer) + return false; + + // Elements that should not have children + switch (roleValue()) { + case ImageRole: + case ButtonRole: + case PopUpButtonRole: + case CheckBoxRole: + case RadioButtonRole: + case TabRole: + case StaticTextRole: + case ListBoxOptionRole: + case ScrollBarRole: + return false; + default: + return true; + } +} + +void AccessibilityRenderObject::clearChildren() +{ + AccessibilityObject::clearChildren(); + m_childrenDirty = false; +} + +void AccessibilityRenderObject::updateChildrenIfNecessary() +{ + if (needsToUpdateChildren()) + clearChildren(); + + AccessibilityObject::updateChildrenIfNecessary(); +} + +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityRenderObject::children() +{ + updateChildrenIfNecessary(); + + return m_children; +} + +void AccessibilityRenderObject::addChildren() +{ + // If the need to add more children in addition to existing children arises, + // childrenChanged should have been called, leaving the object with no children. + ASSERT(!m_haveChildren); + + // nothing to add if there is no RenderObject + if (!m_renderer) + return; + + m_haveChildren = true; + + if (!canHaveChildren()) + return; + + // add all unignored acc children + for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling()) { + if (obj->accessibilityIsIgnored()) { + obj->updateChildrenIfNecessary(); + AccessibilityChildrenVector children = obj->children(); + unsigned length = children.size(); + for (unsigned i = 0; i < length; ++i) + m_children.append(children[i]); + } else { + ASSERT(obj->parentObject() == this); + m_children.append(obj); + } + } + + // FrameView's need to be inserted into the AX hierarchy when encountered. + if (isAttachment()) { + Widget* widget = widgetForAttachmentView(); + if (widget && widget->isFrameView()) + m_children.append(axObjectCache()->getOrCreate(widget)); + } + + // for a RenderImage, add the <area> elements as individual accessibility objects + RenderBoxModelObject* cssBox = renderBoxModelObject(); + if (cssBox && cssBox->isRenderImage()) { + HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); + if (map) { + for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) { + + // add an <area> element for this child if it has a link + if (current->hasTagName(areaTag) && current->isLink()) { + AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole)); + areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(current)); + areaObject->setHTMLMapElement(map); + areaObject->setParent(this); + + m_children.append(areaObject); + } + } + } + } +} + +const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const +{ + DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive")); + DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite")); + DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off")); + + const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); + // These roles have implicit live region status. + if (liveRegionStatus.isEmpty()) { + switch (roleValue()) { + case ApplicationAlertDialogRole: + case ApplicationAlertRole: + return liveRegionStatusAssertive; + case ApplicationLogRole: + case ApplicationStatusRole: + return liveRegionStatusPolite; + case ApplicationTimerRole: + case ApplicationMarqueeRole: + return liveRegionStatusOff; + default: + break; + } + } + + return liveRegionStatus; +} + +const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const +{ + DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text")); + const AtomicString& relevant = getAttribute(aria_relevantAttr); + + // Default aria-relevant = "additions text". + if (relevant.isEmpty()) + return defaultLiveRegionRelevant; + + return relevant; +} + +bool AccessibilityRenderObject::ariaLiveRegionAtomic() const +{ + return elementAttributeValue(aria_atomicAttr); +} + +bool AccessibilityRenderObject::ariaLiveRegionBusy() const +{ + return elementAttributeValue(aria_busyAttr); +} + +void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) +{ + // Get all the rows. + AccessibilityChildrenVector allRows; + ariaTreeRows(allRows); + + // Determine which rows are selected. + bool isMulti = isMultiSelectable(); + + // Prefer active descendant over aria-selected. + AccessibilityObject* activeDesc = activeDescendant(); + if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) { + result.append(activeDesc); + if (!isMulti) + return; + } + + unsigned count = allRows.size(); + for (unsigned k = 0; k < count; ++k) { + if (allRows[k]->isSelected()) { + result.append(allRows[k]); + if (!isMulti) + break; + } + } +} + +void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result) +{ + bool isMulti = isMultiSelectable(); + + AccessibilityChildrenVector childObjects = children(); + unsigned childrenSize = childObjects.size(); + for (unsigned k = 0; k < childrenSize; ++k) { + // Every child should have aria-role option, and if so, check for selected attribute/state. + AccessibilityObject* child = childObjects[k].get(); + if (child->isSelected() && child->ariaRoleAttribute() == ListBoxOptionRole) { + result.append(child); + if (!isMulti) + return; + } + } +} + +void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result) +{ + ASSERT(result.isEmpty()); + + // only listboxes should be asked for their selected children. + 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 || role == TreeGridRole || role == TableRole) + ariaSelectedRows(result); +} + +void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) +{ + if (!hasChildren()) + addChildren(); + + unsigned length = m_children.size(); + for (unsigned i = 0; i < length; i++) { + if (!m_children[i]->isOffScreen()) + result.append(m_children[i]); + } +} + +void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result) +{ + ASSERT(result.isEmpty()); + + // only listboxes are asked for their visible children. + if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes + ASSERT_NOT_REACHED(); + return; + } + 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. + DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); + DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); + DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); + DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); + DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); + DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); + 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; + } +} + +void AccessibilityRenderObject::setAccessibleName(String& name) +{ + // Setting the accessible name can store the value in the DOM + if (!m_renderer) + return; + + Node* domNode = 0; + // For web areas, set the aria-label on the HTML element. + if (isWebArea()) + domNode = m_renderer->document()->documentElement(); + else + domNode = m_renderer->node(); + + if (domNode && domNode->isElementNode()) + static_cast<Element*>(domNode)->setAttribute(aria_labelAttr, name); +} + +void AccessibilityRenderObject::updateBackingStore() +{ + if (!m_renderer) + return; + + // Updating layout may delete m_renderer and this object. + 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(); +} + +static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer) +{ + if (!renderer) + return UnknownRole; + + if (renderer->isText()) + return EditableTextRole; + + if (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListItem()) + return ListItemRole; + + return UnknownRole; +} + +AccessibilityRole AccessibilityRenderObject::roleValueForMSAA() const +{ + if (m_roleForMSAA != UnknownRole) + return m_roleForMSAA; + + m_roleForMSAA = msaaRoleForRenderer(m_renderer); + + if (m_roleForMSAA == UnknownRole) + m_roleForMSAA = roleValue(); + + return m_roleForMSAA; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.h b/Source/WebCore/accessibility/AccessibilityRenderObject.h new file mode 100644 index 0000000..3a29fe9 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.h @@ -0,0 +1,332 @@ + +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityRenderObject_h +#define AccessibilityRenderObject_h + +#include "AccessibilityObject.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class AXObjectCache; +class Element; +class Frame; +class FrameView; +class HitTestResult; +class HTMLAnchorElement; +class HTMLAreaElement; +class HTMLElement; +class HTMLLabelElement; +class HTMLMapElement; +class HTMLSelectElement; +class IntPoint; +class IntSize; +class Node; +class RenderListBox; +class RenderTextControl; +class RenderView; +class VisibleSelection; +class Widget; + +class AccessibilityRenderObject : public AccessibilityObject { +protected: + AccessibilityRenderObject(RenderObject*); +public: + static PassRefPtr<AccessibilityRenderObject> create(RenderObject*); + virtual ~AccessibilityRenderObject(); + + bool isAccessibilityRenderObject() const { return true; } + + virtual bool isAnchor() const; + virtual bool isAttachment() const; + virtual bool isHeading() const; + virtual bool isLink() const; + virtual bool isImageButton() const; + virtual bool isImage() const; + virtual bool isNativeImage() const; + virtual bool isPasswordField() const; + virtual bool isNativeTextControl() const; + virtual bool isWebArea() const; + virtual bool isFileUploadButton() const; + virtual bool isInputImage() const; + virtual bool isProgressIndicator() const; + virtual bool isSlider() const; + virtual bool isMenuRelated() const; + virtual bool isMenu() const; + virtual bool isMenuBar() const; + virtual bool isMenuButton() const; + virtual bool isMenuItem() const; + virtual bool isControl() const; + virtual bool isFieldset() const; + virtual bool isGroup() const; + + virtual bool isEnabled() const; + virtual bool isSelected() const; + virtual bool isFocused() const; + virtual bool isChecked() const; + virtual bool isHovered() const; + virtual bool isIndeterminate() const; + virtual bool isLoaded() const; + virtual bool isMultiSelectable() const; + virtual bool isOffScreen() const; + virtual bool isPressed() const; + virtual bool isReadOnly() const; + virtual bool isVisited() const; + virtual bool isRequired() const; + virtual bool isLinked() const; + virtual void setIsExpanded(bool); + + virtual bool canSetFocusAttribute() const; + virtual bool canSetTextRangeAttributes() const; + virtual bool canSetValueAttribute() const; + virtual bool canSetExpandedAttribute() const; + + virtual void setAccessibleName(String&); + + // Provides common logic used by all elements when determining isIgnored. + AccessibilityObjectInclusion accessibilityIsIgnoredBase() const; + virtual bool accessibilityIsIgnored() const; + + virtual int headingLevel() const; + virtual AccessibilityButtonState checkboxOrRadioValue() const; + virtual String valueDescription() const; + virtual float valueForRange() const; + virtual float maxValueForRange() const; + virtual float minValueForRange() const; + virtual AccessibilityObject* selectedRadioButton(); + virtual AccessibilityObject* selectedTabItem(); + virtual int layoutCount() const; + virtual double estimatedLoadingProgress() const; + + virtual AccessibilityObject* firstChild() const; + virtual AccessibilityObject* lastChild() const; + virtual AccessibilityObject* previousSibling() const; + virtual AccessibilityObject* nextSibling() const; + virtual AccessibilityObject* parentObject() const; + virtual AccessibilityObject* parentObjectIfExists() const; + virtual AccessibilityObject* observableObject() const; + virtual void linkedUIElements(AccessibilityChildrenVector&) const; + virtual bool exposesTitleUIElement() const; + virtual AccessibilityObject* titleUIElement() const; + 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; + void updateAccessibilityRole(); + + // Should be called on the root accessibility object to kick off a hit test. + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const; + + virtual Element* actionElement() const; + Element* mouseButtonListener() const; + FrameView* frameViewIfRenderView() const; + virtual Element* anchorElement() const; + AccessibilityObject* menuForMenuButton() const; + AccessibilityObject* menuButtonForMenu() const; + + virtual IntRect boundingBoxRect() const; + virtual IntRect elementRect() const; + virtual IntSize size() const; + virtual IntPoint clickPoint() const; + + void setRenderer(RenderObject* renderer) { m_renderer = renderer; } + RenderObject* renderer() const { return m_renderer; } + RenderBoxModelObject* renderBoxModelObject() const; + virtual Node* node() const; + + RenderView* topRenderer() const; + RenderTextControl* textControl() const; + Document* document() const; + FrameView* topDocumentFrameView() const; + HTMLLabelElement* labelElementContainer() const; + + virtual KURL url() const; + virtual PlainTextRange selectedTextRange() const; + virtual VisibleSelection selection() const; + virtual String stringValue() const; + virtual String ariaLabeledByAttribute() const; + virtual String title() const; + virtual String ariaDescribedByAttribute() const; + virtual String accessibilityDescription() const; + virtual String helpText() const; + virtual String textUnderElement() const; + virtual String text() const; + virtual int textLength() const; + virtual String selectedText() const; + virtual const AtomicString& accessKey() const; + virtual const String& actionVerb() const; + virtual Widget* widget() const; + virtual Widget* widgetForAttachmentView() const; + virtual void getDocumentLinks(AccessibilityChildrenVector&); + virtual FrameView* documentFrameView() const; + virtual unsigned hierarchicalLevel() const; + + virtual const AccessibilityChildrenVector& children(); + virtual void clearChildren(); + virtual void updateChildrenIfNecessary(); + + 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(); + + virtual void detach(); + virtual void childrenChanged(); + virtual void contentChanged(); + virtual void addChildren(); + 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(); + virtual void handleAriaExpandedChanged(); + + virtual VisiblePositionRange visiblePositionRange() const; + 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 ariaHasPopup() const; + + virtual bool supportsARIADropping() const; + virtual bool supportsARIADragging() const; + 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; + virtual int index(const VisiblePosition&) const; + + virtual VisiblePosition visiblePositionForIndex(int) const; + virtual int indexForVisiblePosition(const VisiblePosition&) const; + + virtual PlainTextRange doAXRangeForLine(unsigned) const; + virtual PlainTextRange doAXRangeForIndex(unsigned) const; + + virtual String doAXStringForRange(const PlainTextRange&) const; + 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; + virtual AccessibilityRole roleValueForMSAA() 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; + bool needsToUpdateChildren() const { return m_childrenDirty; } + + virtual bool isDetached() const { return !m_renderer; } + +private: + void ariaListboxSelectedChildren(AccessibilityChildrenVector&); + void ariaListboxVisibleChildren(AccessibilityChildrenVector&); + bool ariaIsHidden() const; + bool isDescendantOfBarrenParent() const; + bool isAllowedChildOfTree() const; + bool hasTextAlternative() const; + String positionalDescriptionForMSAA() const; + PlainTextRange ariaSelectedTextRange() const; + + Element* menuElementForMenuButton() const; + Element* menuItemElementForMenu() const; + AccessibilityRole determineAccessibilityRole(); + AccessibilityRole determineAriaRoleAttribute() const; + + bool isTabItemSelected() const; + bool isNativeCheckboxOrRadio() const; + IntRect checkboxOrRadioRect() const; + void addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const; + AccessibilityObject* internalLinkElement() const; + AccessibilityObject* accessibilityImageMapHitTest(HTMLAreaElement*, const IntPoint&) const; + AccessibilityObject* accessibilityParentForImageMap(HTMLMapElement*) const; + bool renderObjectIsObservable(RenderObject*) const; + RenderObject* renderParentObject() const; + + void ariaSelectedRows(AccessibilityChildrenVector&); + + bool elementAttributeValue(const QualifiedName&) const; + void setElementAttributeValue(const QualifiedName&, bool); + + String accessibilityDescriptionForElements(Vector<Element*> &elements) const; + void elementsFromAttribute(Vector<Element*>& elements, const QualifiedName&) const; + String ariaAccessibilityDescription() const; + + virtual ESpeak speakProperty() const; + + virtual const AtomicString& ariaLiveRegionStatus() const; + virtual const AtomicString& ariaLiveRegionRelevant() const; + virtual bool ariaLiveRegionAtomic() const; + virtual bool ariaLiveRegionBusy() const; + + bool inheritsPresentationalRole() const; + void setNeedsToUpdateChildren() const { m_childrenDirty = true; } + + mutable AccessibilityRole m_roleForMSAA; +}; + +inline AccessibilityRenderObject* toAccessibilityRenderObject(AccessibilityObject* object) +{ + ASSERT(!object || object->isAccessibilityRenderObject()); + return static_cast<AccessibilityRenderObject*>(object); +} + +inline const AccessibilityRenderObject* toAccessibilityRenderObject(const AccessibilityObject* object) +{ + ASSERT(!object || object->isAccessibilityRenderObject()); + return static_cast<const AccessibilityRenderObject*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toAccessibilityRenderObject(const AccessibilityRenderObject*); + +} // namespace WebCore + +#endif // AccessibilityRenderObject_h diff --git a/Source/WebCore/accessibility/AccessibilityScrollView.cpp b/Source/WebCore/accessibility/AccessibilityScrollView.cpp new file mode 100644 index 0000000..c9dcbb6 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityScrollView.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityScrollView.h" + +#include "AXObjectCache.h" +#include "AccessibilityScrollbar.h" +#include "FrameView.h" +#include "HTMLFrameOwnerElement.h" +#include "RenderPart.h" +#include "ScrollView.h" + +namespace WebCore { + +AccessibilityScrollView::AccessibilityScrollView(ScrollView* view) + : m_scrollView(view) +{ +} + +PassRefPtr<AccessibilityScrollView> AccessibilityScrollView::create(ScrollView* view) +{ + return adoptRef(new AccessibilityScrollView(view)); +} + +AccessibilityObject* AccessibilityScrollView::scrollBar(AccessibilityOrientation orientation) const +{ + switch (orientation) { + case AccessibilityOrientationVertical: + return m_verticalScrollbar ? m_verticalScrollbar.get() : 0; + case AccessibilityOrientationHorizontal: + return m_horizontalScrollbar ? m_horizontalScrollbar.get() : 0; + } + + return 0; +} + +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityScrollView::children() +{ + if (!m_haveChildren) + addChildren(); + return m_children; +} + +void AccessibilityScrollView::updateChildrenIfNecessary() +{ + if (m_scrollView->horizontalScrollbar() && !m_horizontalScrollbar) + m_horizontalScrollbar = addChildScrollbar(m_scrollView->horizontalScrollbar()); + else if (!m_scrollView->horizontalScrollbar() && m_horizontalScrollbar) { + removeChildScrollbar(m_horizontalScrollbar.get()); + m_horizontalScrollbar = 0; + } + + if (m_scrollView->verticalScrollbar() && !m_verticalScrollbar) + m_verticalScrollbar = addChildScrollbar(m_scrollView->verticalScrollbar()); + else if (!m_scrollView->verticalScrollbar() && m_verticalScrollbar) { + removeChildScrollbar(m_verticalScrollbar.get()); + m_verticalScrollbar = 0; + } +} + +void AccessibilityScrollView::removeChildScrollbar(AccessibilityObject* scrollbar) +{ + size_t pos = m_children.find(scrollbar); + if (pos != WTF::notFound) + m_children.remove(pos); +} + +AccessibilityScrollbar* AccessibilityScrollView::addChildScrollbar(Scrollbar* scrollbar) +{ + if (!scrollbar) + return 0; + + AccessibilityScrollbar* scrollBarObject = static_cast<AccessibilityScrollbar*>(axObjectCache()->getOrCreate(scrollbar)); + scrollBarObject->setParent(this); + m_children.append(scrollBarObject); + return scrollBarObject; +} + +void AccessibilityScrollView::addChildren() +{ + ASSERT(!m_haveChildren); + m_haveChildren = true; + + AccessibilityObject* webArea = webAreaObject(); + if (webArea) + m_children.append(webArea); + + updateChildrenIfNecessary(); +} + +AccessibilityObject* AccessibilityScrollView::webAreaObject() const +{ + if (!m_scrollView->isFrameView()) + return 0; + + Document* doc = static_cast<FrameView*>(m_scrollView.get())->frame()->document(); + if (!doc || !doc->renderer()) + return 0; + + return axObjectCache()->getOrCreate(doc->renderer()); +} + +AccessibilityObject* AccessibilityScrollView::accessibilityHitTest(const IntPoint& point) const +{ + AccessibilityObject* webArea = webAreaObject(); + if (!webArea) + return 0; + + if (m_horizontalScrollbar && m_horizontalScrollbar->elementRect().contains(point)) + return m_horizontalScrollbar.get(); + if (m_verticalScrollbar && m_verticalScrollbar->elementRect().contains(point)) + return m_verticalScrollbar.get(); + + return webArea->accessibilityHitTest(point); +} + +Document* AccessibilityScrollView::document() const +{ + if (!m_scrollView->isFrameView()) + return 0; + + return static_cast<FrameView*>(m_scrollView.get())->frame()->document(); +} + +IntRect AccessibilityScrollView::elementRect() const +{ + return m_scrollView->frameRect(); +} + +AccessibilityObject* AccessibilityScrollView::parentObject() const +{ + if (!m_scrollView->isFrameView()) + return 0; + + HTMLFrameOwnerElement* owner = static_cast<FrameView*>(m_scrollView.get())->frame()->ownerElement(); + if (owner && owner->renderPart()) + return axObjectCache()->getOrCreate(owner->renderPart()->parent()); + + return 0; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityScrollView.h b/Source/WebCore/accessibility/AccessibilityScrollView.h new file mode 100644 index 0000000..e32cd1c --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityScrollView.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityScrollView_h +#define AccessibilityScrollView_h + +#include "AccessibilityObject.h" + +namespace WebCore { + +class AccessibilityScrollbar; +class Scrollbar; +class ScrollView; + +class AccessibilityScrollView : public AccessibilityObject { +public: + static PassRefPtr<AccessibilityScrollView> create(ScrollView*); + virtual AccessibilityRole roleValue() const { return ScrollAreaRole; } + ScrollView* scrollView() const { return m_scrollView.get(); } + +private: + AccessibilityScrollView(ScrollView*); + + virtual bool accessibilityIsIgnored() const { return false; } + virtual bool isAccessibilityScrollView() const { return true; } + virtual AccessibilityObject* scrollBar(AccessibilityOrientation) const; + virtual void addChildren(); + virtual Document* document() const; + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const; + virtual const AccessibilityChildrenVector& children(); + virtual void updateChildrenIfNecessary(); + + virtual IntRect elementRect() const; + virtual AccessibilityObject* parentObject() const; + + AccessibilityObject* webAreaObject() const; + AccessibilityScrollbar* addChildScrollbar(Scrollbar*); + void removeChildScrollbar(AccessibilityObject*); + + RefPtr<ScrollView> m_scrollView; + RefPtr<AccessibilityObject> m_horizontalScrollbar; + RefPtr<AccessibilityObject> m_verticalScrollbar; +}; + +inline AccessibilityScrollView* toAccessibilityScrollView(AccessibilityObject* object) +{ + ASSERT(!object || object->isAccessibilityScrollView()); + if (!object->isAccessibilityScrollView()) + return 0; + + return static_cast<AccessibilityScrollView*>(object); +} + +} // namespace WebCore + +#endif // AccessibilityScrollView_h + diff --git a/Source/WebCore/accessibility/AccessibilityScrollbar.cpp b/Source/WebCore/accessibility/AccessibilityScrollbar.cpp new file mode 100644 index 0000000..865797a --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityScrollbar.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityScrollbar.h" + +#include "AXObjectCache.h" +#include "FrameView.h" +#include "ScrollView.h" +#include "Scrollbar.h" + +namespace WebCore { + +AccessibilityScrollbar::AccessibilityScrollbar(Scrollbar* scrollbar) + : m_scrollbar(scrollbar) + , m_parent(0) +{ + ASSERT(scrollbar); +} + +PassRefPtr<AccessibilityScrollbar> AccessibilityScrollbar::create(Scrollbar* scrollbar) +{ + return adoptRef(new AccessibilityScrollbar(scrollbar)); +} + +IntRect AccessibilityScrollbar::elementRect() const +{ + if (!m_scrollbar) + return IntRect(); + + return m_scrollbar->frameRect(); +} + +Document* AccessibilityScrollbar::document() const +{ + AccessibilityObject* parent = parentObject(); + if (!parent) + return 0; + return parent->document(); +} + +AccessibilityOrientation AccessibilityScrollbar::orientation() const +{ + if (!m_scrollbar) + return AccessibilityOrientationHorizontal; + + if (m_scrollbar->orientation() == HorizontalScrollbar) + return AccessibilityOrientationHorizontal; + if (m_scrollbar->orientation() == VerticalScrollbar) + return AccessibilityOrientationVertical; + + return AccessibilityOrientationHorizontal; +} + +bool AccessibilityScrollbar::isEnabled() const +{ + if (!m_scrollbar) + return false; + return m_scrollbar->enabled(); +} + +float AccessibilityScrollbar::valueForRange() const +{ + if (!m_scrollbar) + return 0; + + return m_scrollbar->currentPos() / m_scrollbar->maximum(); +} + +void AccessibilityScrollbar::setValue(float value) +{ + if (!m_scrollbar) + return; + + float newValue = value * m_scrollbar->maximum(); + + m_scrollbar->setValue(newValue, Scrollbar::NotFromScrollAnimator); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityScrollbar.h b/Source/WebCore/accessibility/AccessibilityScrollbar.h new file mode 100644 index 0000000..3268347 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityScrollbar.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityScrollbar_h +#define AccessibilityScrollbar_h + +#include "AccessibilityObject.h" + +namespace WebCore { + +class Scrollbar; + +class AccessibilityScrollbar : public AccessibilityObject { +public: + static PassRefPtr<AccessibilityScrollbar> create(Scrollbar*); + + Scrollbar* scrollbar() const { return m_scrollbar.get(); } + void setParent(AccessibilityObject* parent) { m_parent = parent; } + +private: + AccessibilityScrollbar(Scrollbar*); + + virtual bool accessibilityIsIgnored() const { return false; } + virtual bool canSetValueAttribute() const { return true; } + virtual bool canSetNumericValue() const { return true; } + + virtual bool isAccessibilityScrollbar() const { return true; } + virtual AccessibilityObject* parentObject() const { return m_parent; } + virtual IntRect elementRect() const; + + virtual AccessibilityRole roleValue() const { return ScrollBarRole; } + virtual AccessibilityOrientation orientation() const; + virtual Document* document() const; + virtual bool isEnabled() const; + + // Assumes float [0..1] + virtual void setValue(float); + virtual float valueForRange() const; + + RefPtr<Scrollbar> m_scrollbar; + AccessibilityOrientation m_orientation; + AccessibilityObject* m_parent; +}; + +} // namespace WebCore + +#endif // AccessibilityScrollbar_h diff --git a/Source/WebCore/accessibility/AccessibilitySlider.cpp b/Source/WebCore/accessibility/AccessibilitySlider.cpp new file mode 100644 index 0000000..d4433a2 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilitySlider.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilitySlider.h" + +#include "AXObjectCache.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "RenderObject.h" +#include "RenderSlider.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilitySlider::AccessibilitySlider(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +PassRefPtr<AccessibilitySlider> AccessibilitySlider::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilitySlider(renderer)); +} + +const AccessibilityObject::AccessibilityChildrenVector& AccessibilitySlider::children() +{ + if (!m_haveChildren) + addChildren(); + return m_children; +} + +AccessibilityOrientation AccessibilitySlider::orientation() const +{ + // Default to horizontal in the unknown case. + if (!m_renderer) + return AccessibilityOrientationHorizontal; + + RenderStyle* style = m_renderer->style(); + if (!style) + return AccessibilityOrientationHorizontal; + + ControlPart styleAppearance = style->appearance(); + switch (styleAppearance) { + case SliderThumbHorizontalPart: + case SliderHorizontalPart: + case MediaSliderPart: + return AccessibilityOrientationHorizontal; + + case SliderThumbVerticalPart: + case SliderVerticalPart: + case MediaVolumeSliderPart: + return AccessibilityOrientationVertical; + + default: + return AccessibilityOrientationHorizontal; + } +} + +void AccessibilitySlider::addChildren() +{ + ASSERT(!m_haveChildren); + + m_haveChildren = true; + + AccessibilitySliderThumb* thumb = static_cast<AccessibilitySliderThumb*>(m_renderer->document()->axObjectCache()->getOrCreate(SliderThumbRole)); + thumb->setParentObject(this); + m_children.append(thumb); +} + +const AtomicString& AccessibilitySlider::getAttribute(const QualifiedName& attribute) const +{ + return element()->getAttribute(attribute); +} + +AccessibilityObject* AccessibilitySlider::elementAccessibilityHitTest(const IntPoint& point) const +{ + if (m_children.size()) { + ASSERT(m_children.size() == 1); + if (m_children[0]->elementRect().contains(point)) + return m_children[0].get(); + } + + return axObjectCache()->getOrCreate(m_renderer); +} + +bool AccessibilitySlider::accessibilityIsIgnored() const +{ + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; + + return false; +} + +float AccessibilitySlider::valueForRange() const +{ + return element()->value().toFloat(); +} + +float AccessibilitySlider::maxValueForRange() const +{ + return static_cast<float>(element()->maximum()); +} + +float AccessibilitySlider::minValueForRange() const +{ + return static_cast<float>(element()->minimum()); +} + +void AccessibilitySlider::setValue(const String& value) +{ + HTMLInputElement* input = element(); + + if (input->value() == value) + return; + + input->setValue(value); + + // Fire change event manually, as RenderSlider::setValueForPosition does. + input->dispatchFormControlChangeEvent(); +} + +HTMLInputElement* AccessibilitySlider::element() const +{ + return static_cast<HTMLInputElement*>(m_renderer->node()); +} + + +AccessibilitySliderThumb::AccessibilitySliderThumb() + : m_parentSlider(0) +{ +} + +PassRefPtr<AccessibilitySliderThumb> AccessibilitySliderThumb::create() +{ + return adoptRef(new AccessibilitySliderThumb()); +} + +IntRect AccessibilitySliderThumb::elementRect() const +{ + if (!m_parentSlider->renderer()) + return IntRect(); + + IntRect intRect = toRenderSlider(m_parentSlider->renderer())->thumbRect(); + FloatQuad floatQuad = m_parentSlider->renderer()->localToAbsoluteQuad(FloatRect(intRect)); + + return floatQuad.enclosingBoundingBox(); +} + +IntSize AccessibilitySliderThumb::size() const +{ + return elementRect().size(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilitySlider.h b/Source/WebCore/accessibility/AccessibilitySlider.h new file mode 100644 index 0000000..406c3ab --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilitySlider.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilitySlider_h +#define AccessibilitySlider_h + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class HTMLInputElement; + +class AccessibilitySlider : public AccessibilityRenderObject { + +public: + static PassRefPtr<AccessibilitySlider> create(RenderObject*); + virtual ~AccessibilitySlider() { } + +protected: + AccessibilitySlider(RenderObject*); + +private: + HTMLInputElement* element() const; + virtual bool accessibilityIsIgnored() const; + virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; + + virtual AccessibilityRole roleValue() const { return SliderRole; } + virtual bool isSlider() const { return true; } + virtual bool isInputSlider() const { return true; } + + virtual const AccessibilityChildrenVector& children(); + virtual void addChildren(); + + 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; +}; + +class AccessibilitySliderThumb : public AccessibilityObject { + +public: + static PassRefPtr<AccessibilitySliderThumb> create(); + virtual ~AccessibilitySliderThumb() { } + + virtual AccessibilityRole roleValue() const { return SliderThumbRole; } + + void setParentObject(AccessibilitySlider* slider) { m_parentSlider = slider; } + virtual AccessibilityObject* parentObject() const { return m_parentSlider; } + + virtual IntSize size() const; + virtual IntRect elementRect() const; + +private: + AccessibilitySliderThumb(); + virtual bool accessibilityIsIgnored() const { return false; } + + AccessibilitySlider* m_parentSlider; +}; + + +} // namespace WebCore + +#endif // AccessibilitySlider_h diff --git a/Source/WebCore/accessibility/AccessibilityTable.cpp b/Source/WebCore/accessibility/AccessibilityTable.cpp new file mode 100644 index 0000000..aa51f9e --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTable.cpp @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityTable.h" + +#include "AXObjectCache.h" +#include "AccessibilityTableCell.h" +#include "AccessibilityTableColumn.h" +#include "AccessibilityTableHeaderContainer.h" +#include "AccessibilityTableRow.h" +#include "HTMLNames.h" +#include "HTMLTableCaptionElement.h" +#include "HTMLTableCellElement.h" +#include "HTMLTableElement.h" +#include "RenderObject.h" +#include "RenderTable.h" +#include "RenderTableCell.h" +#include "RenderTableSection.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityTable::AccessibilityTable(RenderObject* renderer) + : AccessibilityRenderObject(renderer), + m_headerContainer(0) +{ +#if ACCESSIBILITY_TABLES + m_isAccessibilityTable = isTableExposableThroughAccessibility(); +#else + m_isAccessibilityTable = false; +#endif +} + +AccessibilityTable::~AccessibilityTable() +{ +} + +PassRefPtr<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityTable(renderer)); +} + +bool AccessibilityTable::hasARIARole() const +{ + if (!m_renderer) + return false; + + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole != UnknownRole) + return true; + + return false; +} + +bool AccessibilityTable::isAccessibilityTable() const +{ + if (!m_renderer) + return false; + + return m_isAccessibilityTable; +} + +bool AccessibilityTable::isDataTable() const +{ + if (!m_renderer) + return false; + + // Do not consider it a data table is it has an ARIA role. + if (hasARIARole()) + return false; + + // This employs a heuristic to determine if this table should appear. + // Only "data" tables should be exposed as tables. + // Unfortunately, there is no good way to determine the difference + // between a "layout" table and a "data" table. + + RenderTable* table = toRenderTable(m_renderer); + Node* tableNode = table->node(); + if (!tableNode || !tableNode->hasTagName(tableTag)) + return false; + + // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table + HTMLTableElement* tableElement = static_cast<HTMLTableElement*>(tableNode); + if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) + return true; + + // if someone used "rules" attribute than the table should appear + if (!tableElement->rules().isEmpty()) + return true; + + // go through the cell's and check for tell-tale signs of "data" table status + // cells have borders, or use attributes like headers, abbr, scope or axis + RenderTableSection* firstBody = table->firstBody(); + if (!firstBody) + return false; + + int numCols = firstBody->numColumns(); + int numRows = firstBody->numRows(); + + // if there's only one cell, it's not a good AXTable candidate + if (numRows == 1 && numCols == 1) + return false; + + // store the background color of the table to check against cell's background colors + RenderStyle* tableStyle = table->style(); + if (!tableStyle) + return false; + Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor); + + // check enough of the cells to find if the table matches our criteria + // Criteria: + // 1) must have at least one valid cell (and) + // 2) at least half of cells have borders (or) + // 3) at least half of cells have different bg colors than the table, and there is cell spacing + unsigned validCellCount = 0; + unsigned borderedCellCount = 0; + unsigned backgroundDifferenceCellCount = 0; + + Color alternatingRowColors[5]; + int alternatingRowColorCount = 0; + + int headersInFirstColumnCount = 0; + for (int row = 0; row < numRows; ++row) { + + int headersInFirstRowCount = 0; + for (int col = 0; col < numCols; ++col) { + RenderTableCell* cell = firstBody->primaryCellAt(row, col); + if (!cell) + continue; + Node* cellNode = cell->node(); + if (!cellNode) + continue; + + if (cell->width() < 1 || cell->height() < 1) + continue; + + validCellCount++; + + HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode); + + bool isTHCell = cellElement->hasTagName(thTag); + // If the first row is comprised of all <th> tags, assume it is a data table. + if (!row && isTHCell) + headersInFirstRowCount++; + + // If the first column is comprised of all <tg> tags, assume it is a data table. + if (!col && isTHCell) + headersInFirstColumnCount++; + + // in this case, the developer explicitly assigned a "data" table attribute + if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() + || !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty()) + return true; + + RenderStyle* renderStyle = cell->style(); + if (!renderStyle) + 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)) + 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->visitedDependentColor(CSSPropertyBackgroundColor); + 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 + if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10) + return true; + + // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows. + if (row < 5 && row == alternatingRowColorCount) { + RenderObject* renderRow = cell->parent(); + if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow()) + continue; + RenderStyle* rowRenderStyle = renderRow->style(); + if (!rowRenderStyle) + continue; + Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor); + alternatingRowColors[alternatingRowColorCount] = rowColor; + alternatingRowColorCount++; + } + } + + if (!row && headersInFirstRowCount == numCols && numCols > 1) + return true; + } + + if (headersInFirstColumnCount == numRows && numRows > 1) + return true; + + // if there is less than two valid cells, it's not a data table + if (validCellCount <= 1) + return false; + + // half of the cells had borders, it's a data table + unsigned neededCellCount = validCellCount / 2; + if (borderedCellCount >= neededCellCount) + return true; + + // half had different background colors, it's a data table + if (backgroundDifferenceCellCount >= neededCellCount) + return true; + + // Check if there is an alternating row background color indicating a zebra striped style pattern. + if (alternatingRowColorCount > 2) { + Color firstColor = alternatingRowColors[0]; + for (int k = 1; k < alternatingRowColorCount; k++) { + // If an odd row was the same color as the first row, its not alternating. + if (k % 2 == 1 && alternatingRowColors[k] == firstColor) + return false; + // If an even row is not the same as the first row, its not alternating. + if (!(k % 2) && alternatingRowColors[k] != firstColor) + return false; + } + return true; + } + + return false; +} + +bool AccessibilityTable::isTableExposableThroughAccessibility() const +{ + // The following is a heuristic used to determine if a + // <table> should be exposed as an AXTable. The goal + // is to only show "data" tables. + + if (!m_renderer) + return false; + + // If the developer assigned an aria role to this, then we + // shouldn't expose it as a table, unless, of course, the aria + // role is a table. + if (hasARIARole()) + return false; + + // Gtk+ ATs expect all tables to be exposed as tables. +#if PLATFORM(GTK) + Node* tableNode = toRenderTable(m_renderer)->node(); + return tableNode && tableNode->hasTagName(tableTag); +#endif + + return isDataTable(); +} + +void AccessibilityTable::clearChildren() +{ + AccessibilityRenderObject::clearChildren(); + m_rows.clear(); + m_columns.clear(); +} + +void AccessibilityTable::addChildren() +{ + if (!isAccessibilityTable()) { + AccessibilityRenderObject::addChildren(); + return; + } + + ASSERT(!m_haveChildren); + + m_haveChildren = true; + if (!m_renderer || !m_renderer->isTable()) + return; + + RenderTable* table = toRenderTable(m_renderer); + AXObjectCache* axCache = m_renderer->document()->axObjectCache(); + + // go through all the available sections to pull out the rows + // and add them as children + RenderTableSection* tableSection = table->header(); + if (!tableSection) + tableSection = table->firstBody(); + + if (!tableSection) + return; + + RenderTableSection* initialTableSection = tableSection; + + while (tableSection) { + + HashSet<AccessibilityObject*> appendedRows; + + unsigned numRows = tableSection->numRows(); + unsigned numCols = tableSection->numColumns(); + for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { + for (unsigned colIndex = 0; colIndex < numCols; ++colIndex) { + + RenderTableCell* cell = tableSection->primaryCellAt(rowIndex, colIndex); + if (!cell) + continue; + + AccessibilityObject* rowObject = axCache->getOrCreate(cell->parent()); + if (!rowObject->isTableRow()) + continue; + + AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject); + // we need to check every cell for a new row, because cell spans + // can cause us to mess rows if we just check the first column + if (appendedRows.contains(row)) + continue; + + row->setRowIndex((int)m_rows.size()); + m_rows.append(row); + if (!row->accessibilityIsIgnored()) + m_children.append(row); +#if PLATFORM(GTK) + else + m_children.append(row->children()); +#endif + appendedRows.add(row); + } + } + + tableSection = table->sectionBelow(tableSection, true); + } + + // make the columns based on the number of columns in the first body + unsigned length = initialTableSection->numColumns(); + for (unsigned i = 0; i < length; ++i) { + AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole)); + column->setColumnIndex((int)i); + column->setParentTable(this); + m_columns.append(column); + if (!column->accessibilityIsIgnored()) + m_children.append(column); + } + + AccessibilityObject* headerContainerObject = headerContainer(); + if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored()) + m_children.append(headerContainerObject); +} + +AccessibilityObject* AccessibilityTable::headerContainer() +{ + if (m_headerContainer) + return m_headerContainer; + + m_headerContainer = static_cast<AccessibilityTableHeaderContainer*>(axObjectCache()->getOrCreate(TableHeaderContainerRole)); + m_headerContainer->setParentTable(this); + + return m_headerContainer; +} + +AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns() +{ + updateChildrenIfNecessary(); + + return m_columns; +} + +AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows() +{ + updateChildrenIfNecessary(); + + return m_rows; +} + +void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers) +{ + if (!m_renderer) + return; + + updateChildrenIfNecessary(); + + unsigned rowCount = m_rows.size(); + for (unsigned k = 0; k < rowCount; ++k) { + AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject(); + if (!header) + continue; + headers.append(header); + } +} + +void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers) +{ + if (!m_renderer) + return; + + updateChildrenIfNecessary(); + + unsigned colCount = m_columns.size(); + for (unsigned k = 0; k < colCount; ++k) { + AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject(); + if (!header) + continue; + headers.append(header); + } +} + +void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells) +{ + if (!m_renderer) + return; + + updateChildrenIfNecessary(); + + int numRows = m_rows.size(); + for (int row = 0; row < numRows; ++row) { + AccessibilityChildrenVector rowChildren = m_rows[row]->children(); + cells.append(rowChildren); + } +} + +unsigned AccessibilityTable::columnCount() +{ + updateChildrenIfNecessary(); + + return m_columns.size(); +} + +unsigned AccessibilityTable::rowCount() +{ + updateChildrenIfNecessary(); + + return m_rows.size(); +} + +AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, unsigned row) +{ + if (!m_renderer || !m_renderer->isTable()) + return 0; + + updateChildrenIfNecessary(); + + RenderTable* table = toRenderTable(m_renderer); + RenderTableSection* tableSection = table->header(); + if (!tableSection) + tableSection = table->firstBody(); + + RenderTableCell* cell = 0; + unsigned rowCount = 0; + unsigned rowOffset = 0; + while (tableSection) { + + unsigned numRows = tableSection->numRows(); + unsigned numCols = tableSection->numColumns(); + + rowCount += numRows; + + unsigned sectionSpecificRow = row - rowOffset; + if (row < rowCount && column < numCols && sectionSpecificRow < numRows) { + cell = tableSection->primaryCellAt(sectionSpecificRow, column); + + // we didn't find the cell, which means there's spanning happening + // search backwards to find the spanning cell + if (!cell) { + + // first try rows + for (int testRow = sectionSpecificRow-1; testRow >= 0; --testRow) { + cell = tableSection->primaryCellAt(testRow, column); + // cell overlapped. use this one + if (cell && ((cell->row() + (cell->rowSpan()-1)) >= (int)sectionSpecificRow)) + break; + cell = 0; + } + + if (!cell) { + // try cols + for (int testCol = column-1; testCol >= 0; --testCol) { + cell = tableSection->primaryCellAt(sectionSpecificRow, testCol); + // cell overlapped. use this one + if (cell && ((cell->col() + (cell->colSpan()-1)) >= (int)column)) + break; + cell = 0; + } + } + } + } + + if (cell) + break; + + rowOffset += numRows; + // we didn't find anything between the rows we should have + if (row < rowCount) + break; + tableSection = table->sectionBelow(tableSection, true); + } + + if (!cell) + return 0; + + AccessibilityObject* cellObject = axObjectCache()->getOrCreate(cell); + ASSERT(cellObject->isTableCell()); + + return static_cast<AccessibilityTableCell*>(cellObject); +} + +AccessibilityRole AccessibilityTable::roleValue() const +{ + if (!isAccessibilityTable()) + return AccessibilityRenderObject::roleValue(); + + return TableRole; +} + +bool AccessibilityTable::accessibilityIsIgnored() const +{ + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; + + if (!isAccessibilityTable()) + return AccessibilityRenderObject::accessibilityIsIgnored(); + + return false; +} + +String AccessibilityTable::title() const +{ + if (!isAccessibilityTable()) + return AccessibilityRenderObject::title(); + + String title; + if (!m_renderer) + return title; + + // see if there is a caption + Node* tableElement = m_renderer->node(); + if (tableElement && tableElement->hasTagName(tableTag)) { + HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(tableElement)->caption(); + if (caption) + title = caption->innerText(); + } + + // try the standard + if (title.isEmpty()) + title = AccessibilityRenderObject::title(); + + return title; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTable.h b/Source/WebCore/accessibility/AccessibilityTable.h new file mode 100644 index 0000000..1666367 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTable.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityTable_h +#define AccessibilityTable_h + +#include "AccessibilityRenderObject.h" +#include <wtf/Forward.h> + +#if PLATFORM(MAC) && (defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)) +#define ACCESSIBILITY_TABLES 0 +#else +#define ACCESSIBILITY_TABLES 1 +#endif + +namespace WebCore { + +class AccessibilityTableCell; +class AccessibilityTableHeaderContainer; + +class AccessibilityTable : public AccessibilityRenderObject { + +protected: + AccessibilityTable(RenderObject*); +public: + static PassRefPtr<AccessibilityTable> create(RenderObject*); + virtual ~AccessibilityTable(); + + virtual bool isAccessibilityTable() const; + virtual bool isDataTable() const; + + virtual AccessibilityRole roleValue() const; + virtual bool isAriaTable() const { return false; } + + virtual bool accessibilityIsIgnored() const; + + virtual void addChildren(); + virtual void clearChildren(); + + AccessibilityChildrenVector& columns(); + AccessibilityChildrenVector& rows(); + + virtual bool supportsSelectedRows() { return false; } + unsigned columnCount(); + unsigned rowCount(); + + virtual String title() const; + + // all the cells in the table + void cells(AccessibilityChildrenVector&); + virtual AccessibilityTableCell* cellForColumnAndRow(unsigned column, unsigned row); + + void columnHeaders(AccessibilityChildrenVector&); + void rowHeaders(AccessibilityChildrenVector&); + + // an object that contains, as children, all the objects that act as headers + AccessibilityObject* headerContainer(); + +protected: + AccessibilityChildrenVector m_rows; + AccessibilityChildrenVector m_columns; + + AccessibilityTableHeaderContainer* m_headerContainer; + mutable bool m_isAccessibilityTable; + + bool hasARIARole() const; + bool isTableExposableThroughAccessibility() const; +}; + +} // namespace WebCore + +#endif // AccessibilityTable_h diff --git a/Source/WebCore/accessibility/AccessibilityTableCell.cpp b/Source/WebCore/accessibility/AccessibilityTableCell.cpp new file mode 100644 index 0000000..7ad6063 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableCell.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityTableCell.h" + +#include "AXObjectCache.h" +#include "HTMLNames.h" +#include "RenderObject.h" +#include "RenderTableCell.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityTableCell::AccessibilityTableCell(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityTableCell::~AccessibilityTableCell() +{ +} + +PassRefPtr<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityTableCell(renderer)); +} + +bool AccessibilityTableCell::accessibilityIsIgnored() const +{ + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; + + if (!isTableCell()) + return AccessibilityRenderObject::accessibilityIsIgnored(); + + return false; +} + +AccessibilityObject* AccessibilityTableCell::parentTable() const +{ + if (!m_renderer || !m_renderer->isTableCell()) + return 0; + + // Do not use getOrCreate. parentTable() can be called while the render tree is being modified + // by javascript, and creating a table element may try to access the render tree while in a bad state. + // By using only get() implies that the AXTable must be created before AXTableCells. This should + // always be the case when AT clients access a table. + // https://bugs.webkit.org/show_bug.cgi?id=42652 + return axObjectCache()->get(toRenderTableCell(m_renderer)->table()); +} + +bool AccessibilityTableCell::isTableCell() const +{ + AccessibilityObject* table = parentTable(); + if (!table || !table->isAccessibilityTable()) + return false; + + return true; +} + +AccessibilityRole AccessibilityTableCell::roleValue() const +{ + if (!isTableCell()) + return AccessibilityRenderObject::roleValue(); + + return CellRole; +} + +void AccessibilityTableCell::rowIndexRange(pair<int, int>& rowRange) +{ + if (!m_renderer || !m_renderer->isTableCell()) + return; + + RenderTableCell* renderCell = toRenderTableCell(m_renderer); + rowRange.first = renderCell->row(); + rowRange.second = renderCell->rowSpan(); + + // since our table might have multiple sections, we have to offset our row appropriately + RenderTableSection* section = renderCell->section(); + RenderTable* table = renderCell->table(); + if (!table || !section) + return; + + RenderTableSection* tableSection = table->header(); + if (!tableSection) + tableSection = table->firstBody(); + + unsigned rowOffset = 0; + while (tableSection) { + if (tableSection == section) + break; + rowOffset += tableSection->numRows(); + tableSection = table->sectionBelow(tableSection, true); + } + + rowRange.first += rowOffset; +} + +void AccessibilityTableCell::columnIndexRange(pair<int, int>& columnRange) +{ + if (!m_renderer || !m_renderer->isTableCell()) + return; + + RenderTableCell* renderCell = toRenderTableCell(m_renderer); + columnRange.first = renderCell->col(); + columnRange.second = renderCell->colSpan(); +} + +AccessibilityObject* AccessibilityTableCell::titleUIElement() const +{ + // Try to find if the first cell in this row is a <th>. If it is, + // then it can act as the title ui element. (This is only in the + // case when the table is not appearing as an AXTable.) + if (isTableCell() || !m_renderer || !m_renderer->isTableCell()) + return 0; + + // Table cells that are th cannot have title ui elements, since by definition + // they are title ui elements + Node* node = m_renderer->node(); + if (node && node->hasTagName(thTag)) + return 0; + + RenderTableCell* renderCell = toRenderTableCell(m_renderer); + + // If this cell is in the first column, there is no need to continue. + int col = renderCell->col(); + if (!col) + return 0; + + int row = renderCell->row(); + + RenderTableSection* section = renderCell->section(); + if (!section) + return 0; + + RenderTableCell* headerCell = section->primaryCellAt(row, 0); + if (!headerCell || headerCell == renderCell) + return 0; + + Node* cellElement = headerCell->node(); + if (!cellElement || !cellElement->hasTagName(thTag)) + return 0; + + return axObjectCache()->getOrCreate(headerCell); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableCell.h b/Source/WebCore/accessibility/AccessibilityTableCell.h new file mode 100644 index 0000000..dabbce2 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableCell.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityTableCell_h +#define AccessibilityTableCell_h + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityTableCell : public AccessibilityRenderObject { + +protected: + AccessibilityTableCell(RenderObject*); +public: + static PassRefPtr<AccessibilityTableCell> create(RenderObject*); + virtual ~AccessibilityTableCell(); + + virtual bool isTableCell() const; + virtual AccessibilityRole roleValue() const; + + virtual bool accessibilityIsIgnored() const; + + // fills in the start location and row span of cell + virtual void rowIndexRange(pair<int, int>& rowRange); + // fills in the start location and column span of cell + virtual void columnIndexRange(pair<int, int>& columnRange); + + // if a table cell is not exposed as a table cell, a TH element can + // serve as its title ui element + AccessibilityObject* titleUIElement() const; + +protected: + virtual AccessibilityObject* parentTable() const; + int m_rowIndex; +}; + +} // namespace WebCore + +#endif // AccessibilityTableCell_h diff --git a/Source/WebCore/accessibility/AccessibilityTableColumn.cpp b/Source/WebCore/accessibility/AccessibilityTableColumn.cpp new file mode 100644 index 0000000..f776e40 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableColumn.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityTableColumn.h" + +#include "AXObjectCache.h" +#include "AccessibilityTableCell.h" +#include "HTMLNames.h" +#include "RenderTable.h" +#include "RenderTableCell.h" +#include "RenderTableSection.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityTableColumn::AccessibilityTableColumn() + : m_parentTable(0) +{ +} + +AccessibilityTableColumn::~AccessibilityTableColumn() +{ +} + +PassRefPtr<AccessibilityTableColumn> AccessibilityTableColumn::create() +{ + return adoptRef(new AccessibilityTableColumn()); +} + +void AccessibilityTableColumn::setParentTable(AccessibilityTable* table) +{ + m_parentTable = table; + + clearChildren(); +} + +IntRect AccessibilityTableColumn::elementRect() const +{ + // this will be filled in when addChildren is called + return m_columnRect; +} + +IntSize AccessibilityTableColumn::size() const +{ + return elementRect().size(); +} + +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTableColumn::children() +{ + if (!m_haveChildren) + addChildren(); + return m_children; +} + +AccessibilityObject* AccessibilityTableColumn::headerObject() +{ + if (!m_parentTable) + return 0; + + RenderObject* renderer = m_parentTable->renderer(); + if (!renderer) + return 0; + + if (m_parentTable->isAriaTable()) { + AccessibilityChildrenVector rowChildren = children(); + unsigned childrenCount = rowChildren.size(); + for (unsigned i = 0; i < childrenCount; ++i) { + AccessibilityObject* cell = rowChildren[i].get(); + if (cell->ariaRoleAttribute() == ColumnHeaderRole) + return cell; + } + + return 0; + } + + if (!renderer->isTable()) + return 0; + + RenderTable* table = toRenderTable(renderer); + + AccessibilityObject* headerObject = 0; + + // try the <thead> section first. this doesn't require th tags + headerObject = headerObjectForSection(table->header(), false); + + if (headerObject) + return headerObject; + + // now try for <th> tags in the first body + headerObject = headerObjectForSection(table->firstBody(), true); + + return headerObject; +} + +AccessibilityObject* AccessibilityTableColumn::headerObjectForSection(RenderTableSection* section, bool thTagRequired) +{ + if (!section) + return 0; + + int numCols = section->numColumns(); + if (m_columnIndex >= numCols) + return 0; + + if (!section->numRows()) + return 0; + + RenderTableCell* cell = 0; + // also account for cells that have a span + for (int testCol = m_columnIndex; testCol >= 0; --testCol) { + RenderTableCell* testCell = section->primaryCellAt(0, testCol); + if (!testCell) + continue; + + // we've reached a cell that doesn't even overlap our column + // it can't be our header + if ((testCell->col() + (testCell->colSpan()-1)) < m_columnIndex) + break; + + Node* node = testCell->node(); + if (!node) + continue; + + if (thTagRequired && !node->hasTagName(thTag)) + continue; + + cell = testCell; + } + + if (!cell) + return 0; + + return m_parentTable->axObjectCache()->getOrCreate(cell); +} + +bool AccessibilityTableColumn::accessibilityIsIgnored() const +{ + if (!m_parentTable) + return true; + +#if PLATFORM(GTK) + return true; +#endif + + return m_parentTable->accessibilityIsIgnored(); +} + +void AccessibilityTableColumn::addChildren() +{ + ASSERT(!m_haveChildren); + + m_haveChildren = true; + if (!m_parentTable) + return; + + int numRows = m_parentTable->rowCount(); + + for (int i = 0; i < numRows; i++) { + AccessibilityTableCell* cell = m_parentTable->cellForColumnAndRow(m_columnIndex, i); + if (!cell) + continue; + + // make sure the last one isn't the same as this one (rowspan cells) + if (m_children.size() > 0 && m_children.last() == cell) + continue; + + m_children.append(cell); + m_columnRect.unite(cell->elementRect()); + } +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableColumn.h b/Source/WebCore/accessibility/AccessibilityTableColumn.h new file mode 100644 index 0000000..15d300c --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableColumn.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityTableColumn_h +#define AccessibilityTableColumn_h + +#include "AccessibilityObject.h" +#include "AccessibilityTable.h" +#include "IntRect.h" + +namespace WebCore { + +class RenderTableSection; + +class AccessibilityTableColumn : public AccessibilityObject { + +private: + AccessibilityTableColumn(); +public: + static PassRefPtr<AccessibilityTableColumn> create(); + virtual ~AccessibilityTableColumn(); + + void setParentTable(AccessibilityTable*); + virtual AccessibilityObject* parentObject() const { return m_parentTable; } + AccessibilityObject* headerObject(); + + virtual bool accessibilityIsIgnored() const; + virtual AccessibilityRole roleValue() const { return ColumnRole; } + virtual bool isTableColumn() const { return true; } + + void setColumnIndex(int columnIndex) { m_columnIndex = columnIndex; } + int columnIndex() const { return m_columnIndex; } + + virtual const AccessibilityChildrenVector& children(); + virtual void addChildren(); + + virtual IntSize size() const; + virtual IntRect elementRect() const; + +private: + AccessibilityTable* m_parentTable; + int m_columnIndex; + IntRect m_columnRect; + + AccessibilityObject* headerObjectForSection(RenderTableSection*, bool thTagRequired); +}; + +} // namespace WebCore + +#endif // AccessibilityTableColumn_h diff --git a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp new file mode 100644 index 0000000..dad6963 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityTableHeaderContainer.h" + +#include "AXObjectCache.h" +#include "AccessibilityTable.h" + +using namespace std; + +namespace WebCore { + +AccessibilityTableHeaderContainer::AccessibilityTableHeaderContainer() + : m_parentTable(0) +{ +} + +AccessibilityTableHeaderContainer::~AccessibilityTableHeaderContainer() +{ +} + +PassRefPtr<AccessibilityTableHeaderContainer> AccessibilityTableHeaderContainer::create() +{ + return adoptRef(new AccessibilityTableHeaderContainer()); +} + +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTableHeaderContainer::children() +{ + if (!m_haveChildren) + addChildren(); + return m_children; +} + +IntRect AccessibilityTableHeaderContainer::elementRect() const +{ + // this will be filled in when addChildren is called + return m_headerRect; +} + +IntSize AccessibilityTableHeaderContainer::size() const +{ + return elementRect().size(); +} + +bool AccessibilityTableHeaderContainer::accessibilityIsIgnored() const +{ + if (!m_parentTable) + return true; + +#if PLATFORM(GTK) + return true; +#endif + + return m_parentTable->accessibilityIsIgnored(); +} + +void AccessibilityTableHeaderContainer::addChildren() +{ + ASSERT(!m_haveChildren); + + m_haveChildren = true; + if (!m_parentTable || !m_parentTable->isAccessibilityTable()) + return; + + static_cast<AccessibilityTable*>(m_parentTable)->columnHeaders(m_children); + + unsigned length = m_children.size(); + for (unsigned k = 0; k < length; ++k) + m_headerRect.unite(m_children[k]->elementRect()); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h new file mode 100644 index 0000000..79521c0 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityTableHeaderContainer_h +#define AccessibilityTableHeaderContainer_h + +#include "AccessibilityObject.h" +#include "AccessibilityTable.h" +#include "IntRect.h" + +namespace WebCore { + +class AccessibilityTableHeaderContainer : public AccessibilityObject { + +private: + AccessibilityTableHeaderContainer(); +public: + static PassRefPtr<AccessibilityTableHeaderContainer> create(); + virtual ~AccessibilityTableHeaderContainer(); + + virtual AccessibilityRole roleValue() const { return TableHeaderContainerRole; } + + void setParentTable(AccessibilityTable* table) { m_parentTable = table; } + virtual AccessibilityObject* parentObject() const { return m_parentTable; } + + virtual const AccessibilityChildrenVector& children(); + virtual void addChildren(); + + virtual IntSize size() const; + virtual IntRect elementRect() const; + +private: + AccessibilityTable* m_parentTable; + IntRect m_headerRect; + + virtual bool accessibilityIsIgnored() const; +}; + +} // namespace WebCore + +#endif // AccessibilityTableHeaderContainer_h diff --git a/Source/WebCore/accessibility/AccessibilityTableRow.cpp b/Source/WebCore/accessibility/AccessibilityTableRow.cpp new file mode 100644 index 0000000..09db132 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableRow.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityTableRow.h" + +#include "AXObjectCache.h" +#include "AccessibilityTableCell.h" +#include "HTMLNames.h" +#include "HTMLTableRowElement.h" +#include "RenderObject.h" +#include "RenderTableCell.h" +#include "RenderTableRow.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityTableRow::AccessibilityTableRow(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityTableRow::~AccessibilityTableRow() +{ +} + +PassRefPtr<AccessibilityTableRow> AccessibilityTableRow::create(RenderObject* renderer) +{ + return adoptRef(new AccessibilityTableRow(renderer)); +} + +AccessibilityRole AccessibilityTableRow::roleValue() const +{ + if (!isTableRow()) + return AccessibilityRenderObject::roleValue(); + + return RowRole; +} + +bool AccessibilityTableRow::isTableRow() const +{ + AccessibilityObject* table = parentTable(); + if (!table || !table->isAccessibilityTable()) + return false; + + return true; +} + +bool AccessibilityTableRow::accessibilityIsIgnored() const +{ + AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; + + if (!isTableRow()) + return AccessibilityRenderObject::accessibilityIsIgnored(); + + return false; +} + +AccessibilityObject* AccessibilityTableRow::parentTable() const +{ + if (!m_renderer || !m_renderer->isTableRow()) + return 0; + + // Do not use getOrCreate. parentTable() can be called while the render tree is being modified. + return axObjectCache()->get(toRenderTableRow(m_renderer)->table()); +} + +AccessibilityObject* AccessibilityTableRow::headerObject() +{ + if (!m_renderer || !m_renderer->isTableRow()) + return 0; + + AccessibilityChildrenVector rowChildren = children(); + if (!rowChildren.size()) + return 0; + + // check the first element in the row to see if it is a TH element + AccessibilityObject* cell = rowChildren[0].get(); + if (!cell->isTableCell()) + return 0; + + RenderObject* cellRenderer = static_cast<AccessibilityTableCell*>(cell)->renderer(); + if (!cellRenderer) + return 0; + + Node* cellNode = cellRenderer->node(); + if (!cellNode || !cellNode->hasTagName(thTag)) + return 0; + + return cell; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableRow.h b/Source/WebCore/accessibility/AccessibilityTableRow.h new file mode 100644 index 0000000..29ac935 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTableRow.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityTableRow_h +#define AccessibilityTableRow_h + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityTableRow : public AccessibilityRenderObject { + +protected: + AccessibilityTableRow(RenderObject*); +public: + static PassRefPtr<AccessibilityTableRow> create(RenderObject*); + virtual ~AccessibilityTableRow(); + + virtual bool isTableRow() const; + virtual AccessibilityRole roleValue() const; + virtual bool accessibilityIsIgnored() const; + + // retrieves the "row" header (a th tag in the rightmost column) + virtual AccessibilityObject* headerObject(); + virtual AccessibilityObject* parentTable() const; + + void setRowIndex(int rowIndex) { m_rowIndex = rowIndex; } + int rowIndex() const { return m_rowIndex; } + + // allows the table to add other children that may not originate + // in the row, but their col/row spans overlap into it + void appendChild(AccessibilityObject*); + +private: + int m_rowIndex; +}; + +} // namespace WebCore + +#endif // AccessibilityTableRow_h diff --git a/Source/WebCore/accessibility/chromium/AXObjectCacheChromium.cpp b/Source/WebCore/accessibility/chromium/AXObjectCacheChromium.cpp new file mode 100644 index 0000000..0dddc17 --- /dev/null +++ b/Source/WebCore/accessibility/chromium/AXObjectCacheChromium.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2008 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AXObjectCache.h" +#include "AccessibilityObject.h" +#include "AccessibilityScrollbar.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "FrameView.h" +#include "Scrollbar.h" + +namespace WebCore { + +void AXObjectCache::detachWrapper(AccessibilityObject* obj) +{ + // In Chromium, AccessibilityObjects are wrapped lazily. + if (AccessibilityObjectWrapper* wrapper = obj->wrapper()) + wrapper->detach(); +} + +void AXObjectCache::attachWrapper(AccessibilityObject*) +{ + // In Chromium, AccessibilityObjects are wrapped lazily. +} + +void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotification notification) +{ + if (obj && obj->isAccessibilityScrollbar() && notification == AXValueChanged) { + // Send document value changed on scrollbar value changed notification. + Scrollbar* scrollBar = static_cast<AccessibilityScrollbar*>(obj)->scrollbar(); + if (!scrollBar || !scrollBar->parent() || !scrollBar->parent()->isFrameView()) + return; + Document* document = static_cast<FrameView*>(scrollBar->parent())->frame()->document(); + if (document != document->topDocument()) + return; + obj = get(document->renderer()); + } + + if (!obj || !obj->document() || !obj->documentFrameView() || !obj->documentFrameView()->frame() || !obj->documentFrameView()->frame()->page()) + return; + + ChromeClient* client = obj->documentFrameView()->frame()->page()->chrome()->client(); + if (!client) + return; + + switch (notification) { + case AXActiveDescendantChanged: + if (!obj->document()->focusedNode() || (obj->node() != obj->document()->focusedNode())) + break; + + // Calling handleFocusedUIElementChanged will focus the new active + // descendant and send the AXFocusedUIElementChanged notification. + handleFocusedUIElementChanged(0, obj->document()->focusedNode()->renderer()); + break; + case AXCheckedStateChanged: + case AXChildrenChanged: + case AXFocusedUIElementChanged: + case AXLayoutComplete: + case AXLiveRegionChanged: + case AXLoadComplete: + case AXMenuListValueChanged: + case AXRowCollapsed: + case AXRowCountChanged: + case AXRowExpanded: + case AXScrolledToAnchor: + case AXSelectedChildrenChanged: + case AXSelectedTextChanged: + case AXValueChanged: + case AXInvalidStatusChanged: + break; + } + + client->postAccessibilityNotification(obj, notification); +} + +void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, unsigned) +{ +} + +void AXObjectCache::handleFocusedUIElementChanged(RenderObject*, RenderObject* newFocusedRenderer) +{ + if (!newFocusedRenderer) + return; + + Page* page = newFocusedRenderer->document()->page(); + if (!page) + return; + + AccessibilityObject* focusedObject = focusedUIElementForPage(page); + if (!focusedObject) + return; + + postPlatformNotification(focusedObject, AXFocusedUIElementChanged); +} + +void AXObjectCache::handleScrolledToAnchor(const Node* anchorNode) +{ + // The anchor node may not be accessible. Post the notification for the + // first accessible object. + postPlatformNotification(AccessibilityObject::firstAccessibleObjectFromNode(anchorNode), AXScrolledToAnchor); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/chromium/AccessibilityObjectChromium.cpp b/Source/WebCore/accessibility/chromium/AccessibilityObjectChromium.cpp new file mode 100644 index 0000000..5b4cfd5 --- /dev/null +++ b/Source/WebCore/accessibility/chromium/AccessibilityObjectChromium.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2008 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityObject.h" + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + if (isMenuListPopup() || isMenuListOption()) + return IgnoreObject; + + return DefaultBehavior; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h b/Source/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h new file mode 100644 index 0000000..85a65ed --- /dev/null +++ b/Source/WebCore/accessibility/chromium/AccessibilityObjectWrapper.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2008 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityObjectWrapper_h +#define AccessibilityObjectWrapper_h + +#include <wtf/RefCounted.h> + +namespace WebCore { + + class AccessibilityObject; + class AccessibilityObjectWrapper : public RefCounted<AccessibilityObjectWrapper> { + public: + virtual ~AccessibilityObjectWrapper() {} + virtual void detach() = 0; + bool attached() const { return m_object; } + AccessibilityObject* accessibilityObject() const { return m_object; } + + protected: + AccessibilityObjectWrapper(AccessibilityObject* obj) + : m_object(obj) + { + } + AccessibilityObjectWrapper() : m_object(0) { } + + AccessibilityObject* m_object; + }; + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/accessibility/efl/AccessibilityObjectEfl.cpp b/Source/WebCore/accessibility/efl/AccessibilityObjectEfl.cpp new file mode 100644 index 0000000..d57c3fa --- /dev/null +++ b/Source/WebCore/accessibility/efl/AccessibilityObjectEfl.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008 Apple Ltd. + * Copyright (C) 2009-2010 ProFUSION embedded systems + * Copyright (C) 2009-2010 Samsung Electronics + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObject.h" + +#if HAVE(ACCESSIBILITY) + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +} // namespace WebCore + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp b/Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp new file mode 100644 index 0000000..c341a2d --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AXObjectCache.h" + +#include "AccessibilityObject.h" +#include "AccessibilityObjectWrapperAtk.h" +#include "AccessibilityRenderObject.h" +#include "GOwnPtr.h" +#include "Range.h" +#include "SelectElement.h" +#include "TextIterator.h" + +namespace WebCore { + +void AXObjectCache::detachWrapper(AccessibilityObject* obj) +{ + webkit_accessible_detach(WEBKIT_ACCESSIBLE(obj->wrapper())); +} + +void AXObjectCache::attachWrapper(AccessibilityObject* obj) +{ + AtkObject* atkObj = ATK_OBJECT(webkit_accessible_new(obj)); + obj->setWrapper(atkObj); + g_object_unref(atkObj); +} + +static void notifyChildrenSelectionChange(AccessibilityObject* object) +{ + // This static variable is needed to keep track of the old focused + // object as per previous calls to this function, in order to + // properly decide whether to emit some signals or not. + static RefPtr<AccessibilityObject> oldFocusedObject = 0; + + // Only list boxes supported so far. + if (!object || !object->isListBox()) + return; + + // Emit signal from the listbox's point of view first. + g_signal_emit_by_name(object->wrapper(), "selection-changed"); + + // Find the item where the selection change was triggered from. + AccessibilityObject::AccessibilityChildrenVector items = object->children(); + SelectElement* select = toSelectElement(static_cast<Element*>(object->node())); + if (!select) + return; + int changedItemIndex = select->activeSelectionStartListIndex(); + if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size())) + return; + AccessibilityObject* item = items.at(changedItemIndex).get(); + + // Ensure the oldFocusedObject belongs to the same document that + // the current item so further comparisons make sense. Otherwise, + // just reset oldFocusedObject so it won't be taken into account. + if (item && oldFocusedObject && item->document() != oldFocusedObject->document()) + oldFocusedObject = 0; + + AtkObject* axItem = item ? item->wrapper() : 0; + AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper() : 0; + + // Old focused object just lost focus, so emit the events. + if (axOldFocusedObject && axItem != axOldFocusedObject) { + g_signal_emit_by_name(axOldFocusedObject, "focus-event", false); + g_signal_emit_by_name(axOldFocusedObject, "state-change", "focused", false); + } + + // Emit needed events for the currently (un)selected item. + if (axItem) { + bool isSelected = item->isSelected(); + g_signal_emit_by_name(axItem, "state-change", "selected", isSelected); + g_signal_emit_by_name(axItem, "focus-event", isSelected); + g_signal_emit_by_name(axItem, "state-change", "focused", isSelected); + } + + // Update pointer to the previously focused object. + oldFocusedObject = item; +} + +void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification) +{ + AtkObject* axObject = coreObject->wrapper(); + if (!axObject) + return; + + if (notification == AXCheckedStateChanged) { + if (!coreObject->isCheckboxOrRadio()) + return; + g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->isChecked()); + } else if (notification == AXMenuListValueChanged) { + if (!coreObject->isMenuList()) + return; + g_signal_emit_by_name(axObject, "focus-event", true); + g_signal_emit_by_name(axObject, "state-change", "focused", true); + } else if (notification == AXSelectedChildrenChanged) + notifyChildrenSelectionChange(coreObject); +} + +static void emitTextChanged(AccessibilityRenderObject* object, AXObjectCache::AXTextChange textChange, unsigned offset, unsigned count) +{ + // Get the axObject for the parent object + AtkObject* wrapper = object->parentObjectUnignored()->wrapper(); + if (!wrapper || !ATK_IS_TEXT(wrapper)) + return; + + // Select the right signal to be emitted + CString detail; + switch (textChange) { + case AXObjectCache::AXTextInserted: + detail = "text-changed::insert"; + break; + case AXObjectCache::AXTextDeleted: + detail = "text-changed::delete"; + break; + } + + if (!detail.isNull()) + g_signal_emit_by_name(wrapper, detail.data(), offset, count); +} + +void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, unsigned count) +{ + // Sanity check + if (count < 1 || !object || !object->isAccessibilityRenderObject()) + return; + + Node* node = object->node(); + RefPtr<Range> range = Range::create(node->document(), Position(node->parentNode(), 0), Position(node, 0)); + emitTextChanged(toAccessibilityRenderObject(object), textChange, offset + TextIterator::rangeLength(range.get()), count); +} + +void AXObjectCache::handleFocusedUIElementChanged(RenderObject* oldFocusedRender, RenderObject* newFocusedRender) +{ + RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedRender); + if (oldObject) { + g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false); + g_signal_emit_by_name(oldObject->wrapper(), "state-change", "focused", false); + } + RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedRender); + if (newObject) { + g_signal_emit_by_name(newObject->wrapper(), "focus-event", true); + g_signal_emit_by_name(newObject->wrapper(), "state-change", "focused", true); + } +} + +void AXObjectCache::handleScrolledToAnchor(const Node*) +{ +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp b/Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp new file mode 100644 index 0000000..9772b43 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2008 Apple Ltd. + * Copyright (C) 2008 Alp Toker <alp@atoker.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObject.h" +#include "RenderObject.h" +#include "RenderText.h" + +#include <glib-object.h> + +#if HAVE(ACCESSIBILITY) + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + AccessibilityObject* parent = parentObject(); + if (!parent) + return DefaultBehavior; + + if (roleValue() == SplitterRole) + return IncludeObject; + + if (isGroup()) { + // When a list item is made up entirely of children (e.g. paragraphs) + // the list item gets ignored. We need it. + if (parent->isList()) + return IncludeObject; + + // We expect the parent of a table cell to be a table. + AccessibilityObject* child = firstChild(); + if (child && child->roleValue() == CellRole) + return IgnoreObject; + } + + // Entries and password fields have extraneous children which we want to ignore. + if (parent->isPasswordField() || parent->isTextControl()) + return IgnoreObject; + + AccessibilityRole role = roleValue(); + + // Include all tables, even layout tables. The AT can decide what to do with each. + if (role == CellRole || role == TableRole) + return IncludeObject; + + // We at some point might have a need to expose a table row; but it's not standard Gtk+. + if (role == RowRole) + return IgnoreObject; + + // The object containing the text should implement AtkText itself. + if (role == StaticTextRole) + return IgnoreObject; + + // Include all list items, regardless they have or not inline children + if (role == ListItemRole) + return IncludeObject; + + // Bullets/numbers for list items shouldn't be exposed as AtkObjects. + if (role == ListMarkerRole) + return IgnoreObject; + + return DefaultBehavior; +} + +AccessibilityObjectWrapper* AccessibilityObject::wrapper() const +{ + return m_wrapper; +} + +void AccessibilityObject::setWrapper(AccessibilityObjectWrapper* wrapper) +{ + if (wrapper == m_wrapper) + return; + + if (m_wrapper) + g_object_unref(m_wrapper); + + m_wrapper = wrapper; + + if (m_wrapper) + g_object_ref(m_wrapper); +} + +bool AccessibilityObject::allowsTextRanges() const +{ + return isTextControl() || isWebArea() || isGroup() || isLink() || isHeading(); +} + +unsigned AccessibilityObject::getLengthForTextRange() const +{ + unsigned textLength = text().length(); + + if (textLength) + return textLength; + + // Gtk ATs need this for all text objects; not just text controls. + Node* node = this->node(); + RenderObject* renderer = node ? node->renderer() : 0; + if (renderer && renderer->isText()) { + RenderText* renderText = toRenderText(renderer); + textLength = renderText ? renderText->textLength() : 0; + } + + // Get the text length from the elements under the + // accessibility object if the value is still zero. + if (!textLength && allowsTextRanges()) + textLength = textUnderElement().length(); + + return textLength; +} + +} // namespace WebCore + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp new file mode 100644 index 0000000..7489034 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp @@ -0,0 +1,2489 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * Copyright (C) 2009 Igalia S.L. + * Copyright (C) 2009 Jan Alonzo + * + * Portions from Mozilla a11y, copyright as follows: + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObjectWrapperAtk.h" + +#if HAVE(ACCESSIBILITY) + +#include "AXObjectCache.h" +#include "AccessibilityList.h" +#include "AccessibilityListBox.h" +#include "AccessibilityListBoxOption.h" +#include "AccessibilityRenderObject.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableCell.h" +#include "AccessibilityTableColumn.h" +#include "AccessibilityTableRow.h" +#include "Document.h" +#include "DocumentType.h" +#include "Editor.h" +#include "Frame.h" +#include "FrameView.h" +#include "GOwnPtr.h" +#include "HostWindow.h" +#include "HTMLNames.h" +#include "HTMLTableCaptionElement.h" +#include "HTMLTableElement.h" +#include "InlineTextBox.h" +#include "IntRect.h" +#include "NotImplemented.h" +#include "RenderListItem.h" +#include "RenderListMarker.h" +#include "RenderText.h" +#include "SelectElement.h" +#include "Settings.h" +#include "TextEncoding.h" +#include "TextIterator.h" +#include "WebKitAccessibleHyperlink.h" + +#include <atk/atk.h> +#include <glib.h> +#include <glib/gprintf.h> +#include <libgail-util/gail-util.h> +#include <pango/pango.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/CString.h> + +using namespace WebCore; + +static AccessibilityObject* fallbackObject() +{ + // FIXME: An AXObjectCache with a Document is meaningless. + static AXObjectCache* fallbackCache = new AXObjectCache(0); + static AccessibilityObject* object = 0; + if (!object) { + // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack + object = fallbackCache->getOrCreate(ListBoxOptionRole); + object->ref(); + } + + return object; +} + +// Used to provide const char* returns. +static const char* returnString(const String& str) +{ + static CString returnedString; + returnedString = str.utf8(); + return returnedString.data(); +} + +static AccessibilityObject* core(WebKitAccessible* accessible) +{ + if (!accessible) + return 0; + + return accessible->m_object; +} + +static AccessibilityObject* core(AtkObject* object) +{ + if (!WEBKIT_IS_ACCESSIBLE(object)) + return 0; + + return core(WEBKIT_ACCESSIBLE(object)); +} + +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)); +} + +static AccessibilityObject* core(AtkEditableText* text) +{ + return core(ATK_OBJECT(text)); +} + +static AccessibilityObject* core(AtkComponent* component) +{ + return core(ATK_OBJECT(component)); +} + +static AccessibilityObject* core(AtkImage* image) +{ + return core(ATK_OBJECT(image)); +} + +static AccessibilityObject* core(AtkTable* table) +{ + return core(ATK_OBJECT(table)); +} + +static AccessibilityObject* core(AtkHypertext* hypertext) +{ + return core(ATK_OBJECT(hypertext)); +} + +static AccessibilityObject* core(AtkDocument* document) +{ + return core(ATK_OBJECT(document)); +} + +static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset); + +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()) { + AccessibilityObject* label = renderObject->correspondingLabelForControlElement(); + if (label) { + AtkObject* atkObject = label->wrapper(); + if (ATK_IS_TEXT(atkObject)) + return webkit_accessible_text_get_text(ATK_TEXT(atkObject), 0, -1); + } + + // Try text under the node + String textUnder = renderObject->textUnderElement(); + if (textUnder.length()) + return returnString(textUnder); + } + + 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) { + 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()); +} + +static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) +{ + AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); + if (accObject->isControl()) { + AccessibilityObject* label = accObject->correspondingLabelForControlElement(); + if (label) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); + } else { + AccessibilityObject* control = accObject->correspondingControlForLabelElement(); + if (control) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); + } +} + +static gpointer webkit_accessible_parent_class = 0; + +static bool isRootObject(AccessibilityObject* coreObject) +{ + // The root accessible object in WebCore is always an object with + // the ScrolledArea role with one child with the WebArea role. + if (!coreObject || !coreObject->isScrollView()) + return false; + + AccessibilityObject* firstChild = coreObject->firstChild(); + if (!firstChild || !firstChild->isWebArea()) + return false; + + return true; +} + +static AtkObject* atkParentOfRootObject(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject* coreParent = coreObject->parentObjectUnignored(); + + // The top level object claims to not have a parent. This makes it + // impossible for assistive technologies to ascend the accessible + // hierarchy all the way to the application. (Bug 30489) + if (!coreParent && isRootObject(coreObject)) { + HostWindow* hostWindow = coreObject->document()->view()->hostWindow(); + if (hostWindow) { + PlatformPageClient scrollView = hostWindow->platformPageClient(); + if (scrollView) { + GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView); + if (scrollViewParent) + return gtk_widget_get_accessible(scrollViewParent); + } + } + } + + if (!coreParent) + return 0; + + return coreParent->wrapper(); +} + +static AtkObject* webkit_accessible_get_parent(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject* coreParent = coreObject->parentObjectUnignored(); + if (!coreParent && isRootObject(coreObject)) + return atkParentOfRootObject(object); + + if (!coreParent) + return 0; + + return coreParent->wrapper(); +} + +static gint webkit_accessible_get_n_children(AtkObject* object) +{ + return core(object)->children().size(); +} + +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; + + AccessibilityObject* coreChild = children.at(index).get(); + + if (!coreChild) + return 0; + + AtkObject* child = coreChild->wrapper(); + atk_object_set_parent(child, object); + g_object_ref(child); + + return child; +} + +static gint webkit_accessible_get_index_in_parent(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject* parent = coreObject->parentObjectUnignored(); + + if (!parent && isRootObject(coreObject)) { + AtkObject* atkParent = atkParentOfRootObject(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(); + for (unsigned i = 0; i < count; ++i) { + if (children[i] == coreObject) + return i; + } + + return -1; +} + +static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const char* name, const char* value) +{ + AtkAttribute* attribute = static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute))); + attribute->name = g_strdup(name); + attribute->value = g_strdup(value); + attributeSet = g_slist_prepend(attributeSet, attribute); + + return attributeSet; +} + +static AtkAttributeSet* webkit_accessible_get_attributes(AtkObject* object) +{ + AtkAttributeSet* attributeSet = 0; + attributeSet = addAttributeToSet(attributeSet, "toolkit", "WebKitGtk"); + + AccessibilityObject* coreObject = core(object); + if (!coreObject) + return attributeSet; + + int headingLevel = coreObject->headingLevel(); + if (headingLevel) { + String value = String::number(headingLevel); + attributeSet = addAttributeToSet(attributeSet, "level", value.utf8().data()); + } + + // Set the 'layout-guess' attribute to help Assistive + // Technologies know when an exposed table is not data table. + if (coreObject->isAccessibilityTable() && !coreObject->isDataTable()) + attributeSet = addAttributeToSet(attributeSet, "layout-guess", "true"); + + return attributeSet; +} + +static AtkRole atkRole(AccessibilityRole role) +{ + switch (role) { + case UnknownRole: + return ATK_ROLE_UNKNOWN; + case ButtonRole: + return ATK_ROLE_PUSH_BUTTON; + case RadioButtonRole: + return ATK_ROLE_RADIO_BUTTON; + case CheckBoxRole: + return ATK_ROLE_CHECK_BOX; + case SliderRole: + return ATK_ROLE_SLIDER; + case TabGroupRole: + return ATK_ROLE_PAGE_TAB_LIST; + case TextFieldRole: + case TextAreaRole: + return ATK_ROLE_ENTRY; + case StaticTextRole: + return ATK_ROLE_TEXT; + case OutlineRole: + return ATK_ROLE_TREE; + case MenuBarRole: + return ATK_ROLE_MENU_BAR; + case MenuListPopupRole: + case MenuRole: + return ATK_ROLE_MENU; + case MenuListOptionRole: + case MenuItemRole: + return ATK_ROLE_MENU_ITEM; + case ColumnRole: + //return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? + return ATK_ROLE_UNKNOWN; // Matches Mozilla + case RowRole: + //return ATK_ROLE_TABLE_ROW_HEADER; // Is this right? + return ATK_ROLE_LIST_ITEM; // Matches Mozilla + case ToolbarRole: + return ATK_ROLE_TOOL_BAR; + case BusyIndicatorRole: + return ATK_ROLE_PROGRESS_BAR; // Is this right? + case ProgressIndicatorRole: + //return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp + return ATK_ROLE_PROGRESS_BAR; + case WindowRole: + return ATK_ROLE_WINDOW; + case PopUpButtonRole: + case ComboBoxRole: + return ATK_ROLE_COMBO_BOX; + case SplitGroupRole: + return ATK_ROLE_SPLIT_PANE; + case SplitterRole: + return ATK_ROLE_SEPARATOR; + case ColorWellRole: + return ATK_ROLE_COLOR_CHOOSER; + case ListRole: + return ATK_ROLE_LIST; + case ScrollBarRole: + return ATK_ROLE_SCROLL_BAR; + case ScrollAreaRole: + return ATK_ROLE_SCROLL_PANE; + case GridRole: // Is this right? + case TableRole: + return ATK_ROLE_TABLE; + case ApplicationRole: + return ATK_ROLE_APPLICATION; + case GroupRole: + case RadioGroupRole: + return ATK_ROLE_PANEL; + case CellRole: + return ATK_ROLE_TABLE_CELL; + case LinkRole: + case WebCoreLinkRole: + case ImageMapLinkRole: + return ATK_ROLE_LINK; + case ImageMapRole: + case ImageRole: + return ATK_ROLE_IMAGE; + case ListMarkerRole: + return ATK_ROLE_TEXT; + case WebAreaRole: + //return ATK_ROLE_HTML_CONTAINER; // Is this right? + return ATK_ROLE_DOCUMENT_FRAME; + case HeadingRole: + return ATK_ROLE_HEADING; + case ListBoxRole: + return ATK_ROLE_LIST; + case ListItemRole: + case ListBoxOptionRole: + return ATK_ROLE_LIST_ITEM; + default: + return ATK_ROLE_UNKNOWN; + } +} + +static AtkRole webkit_accessible_get_role(AtkObject* object) +{ + AccessibilityObject* axObject = core(object); + + if (!axObject) + return ATK_ROLE_UNKNOWN; + + // WebCore does not know about paragraph role, label role, or section role + if (axObject->isAccessibilityRenderObject()) { + Node* node = static_cast<AccessibilityRenderObject*>(axObject)->renderer()->node(); + if (node) { + if (node->hasTagName(HTMLNames::pTag)) + return ATK_ROLE_PARAGRAPH; + if (node->hasTagName(HTMLNames::labelTag)) + return ATK_ROLE_LABEL; + if (node->hasTagName(HTMLNames::divTag)) + return ATK_ROLE_SECTION; + if (node->hasTagName(HTMLNames::formTag)) + return ATK_ROLE_FORM; + } + } + + // Note: Why doesn't WebCore have a password field for this + if (axObject->isPasswordField()) + return ATK_ROLE_PASSWORD_TEXT; + + return atkRole(axObject->roleValue()); +} + +static bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection) +{ + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return false; + + if (selection.isNone()) + return false; + + RefPtr<Range> range = selection.toNormalizedRange(); + if (!range) + return false; + + // We want to check that both the selection intersects the node + // AND that the selection is not just "touching" one of the + // boundaries for the selected node. We want to check whether the + // node is actually inside the region, at least partially. + Node* node = coreObject->node(); + Node* lastDescendant = node->lastDescendant(); + ExceptionCode ec = 0; + return (range->intersectsNode(node, ec) + && (range->endContainer() != node || range->endOffset()) + && (range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant))); +} + +static bool isTextWithCaret(AccessibilityObject* coreObject) +{ + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return false; + + Document* document = coreObject->document(); + if (!document) + return false; + + Frame* frame = document->frame(); + if (!frame) + return false; + + Settings* settings = frame->settings(); + if (!settings || !settings->caretBrowsingEnabled()) + return false; + + // Check text objects and paragraphs only. + AtkObject* axObject = coreObject->wrapper(); + AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID; + if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH) + return false; + + // Finally, check whether the caret is set in the current object. + VisibleSelection selection = coreObject->selection(); + if (!selection.isCaret()) + return false; + + return selectionBelongsToObject(coreObject, selection); +} + +static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet) +{ + AccessibilityObject* parent = coreObject->parentObject(); + bool isListBoxOption = parent && parent->isListBox(); + + // Please keep the state list in alphabetical order + if (coreObject->isChecked()) + atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); + + // FIXME: isReadOnly does not seem to do the right thing for + // controls, so check explicitly for them. In addition, because + // isReadOnly is false for listBoxOptions, we need to add one + // more check so that we do not present them as being "editable". + if ((!coreObject->isReadOnly() || + (coreObject->isControl() && coreObject->canSetValueAttribute())) && + !isListBoxOption) + atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); + + // FIXME: Put both ENABLED and SENSITIVE together here for now + if (coreObject->isEnabled()) { + atk_state_set_add_state(stateSet, ATK_STATE_ENABLED); + atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE); + } + + if (coreObject->canSetExpandedAttribute()) + atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE); + + if (coreObject->isExpanded()) + atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED); + + if (coreObject->canSetFocusAttribute()) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); + + if (coreObject->isFocused() || isTextWithCaret(coreObject)) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); + + // TODO: ATK_STATE_HORIZONTAL + + if (coreObject->isIndeterminate()) + atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); + + if (coreObject->isMultiSelectable()) + atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE); + + // TODO: ATK_STATE_OPAQUE + + if (coreObject->isPressed()) + atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); + + // TODO: ATK_STATE_SELECTABLE_TEXT + + if (coreObject->canSetSelectedAttribute()) { + atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE); + // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED} + // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the + // former. + if (isListBoxOption) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); + } + + if (coreObject->isSelected()) { + atk_state_set_add_state(stateSet, ATK_STATE_SELECTED); + // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED} + // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the + // former. + if (isListBoxOption) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); + } + + // FIXME: Group both SHOWING and VISIBLE here for now + // Not sure how to handle this in WebKit, see bug + // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other + // issues with SHOWING vs VISIBLE within GTK+ + if (!coreObject->isOffScreen()) { + atk_state_set_add_state(stateSet, ATK_STATE_SHOWING); + atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE); + } + + // Mutually exclusive, so we group these two + if (coreObject->roleValue() == TextFieldRole) + atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); + else if (coreObject->roleValue() == TextAreaRole) + atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE); + + // TODO: ATK_STATE_SENSITIVE + + // TODO: ATK_STATE_VERTICAL + + if (coreObject->isVisited()) + atk_state_set_add_state(stateSet, ATK_STATE_VISITED); +} + +static AtkStateSet* webkit_accessible_ref_state_set(AtkObject* object) +{ + AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object); + AccessibilityObject* coreObject = core(object); + + if (coreObject == fallbackObject()) { + atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT); + return stateSet; + } + + // Text objects must be focusable. + AtkRole role = atk_object_get_role(object); + if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); + + setAtkStateSetFromCoreObject(coreObject, stateSet); + return stateSet; +} + +static AtkRelationSet* webkit_accessible_ref_relation_set(AtkObject* object) +{ + AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object); + AccessibilityObject* coreObject = core(object); + + setAtkRelationSetFromCoreObject(coreObject, relationSet); + + return relationSet; +} + +static void webkit_accessible_init(AtkObject* object, gpointer data) +{ + if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize) + ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data); + + WEBKIT_ACCESSIBLE(object)->m_object = reinterpret_cast<AccessibilityObject*>(data); +} + +static void webkit_accessible_finalize(GObject* object) +{ + // This is a good time to clear the return buffer. + returnString(String()); + + G_OBJECT_CLASS(webkit_accessible_parent_class)->finalize(object); +} + +static void webkit_accessible_class_init(AtkObjectClass* klass) +{ + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + + webkit_accessible_parent_class = g_type_class_peek_parent(klass); + + gobjectClass->finalize = webkit_accessible_finalize; + + klass->initialize = webkit_accessible_init; + klass->get_name = webkit_accessible_get_name; + klass->get_description = webkit_accessible_get_description; + klass->get_parent = webkit_accessible_get_parent; + klass->get_n_children = webkit_accessible_get_n_children; + klass->ref_child = webkit_accessible_ref_child; + klass->get_role = webkit_accessible_get_role; + klass->ref_state_set = webkit_accessible_ref_state_set; + klass->get_index_in_parent = webkit_accessible_get_index_in_parent; + klass->get_attributes = webkit_accessible_get_attributes; + klass->ref_relation_set = webkit_accessible_ref_relation_set; +} + +GType +webkit_accessible_get_type(void) +{ + static volatile gsize type_volatile = 0; + + if (g_once_init_enter(&type_volatile)) { + static const GTypeInfo tinfo = { + sizeof(WebKitAccessibleClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) webkit_accessible_class_init, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(WebKitAccessible), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) 0, + 0 /* value table */ + }; + + GType type = g_type_register_static(ATK_TYPE_OBJECT, + "WebKitAccessible", &tinfo, GTypeFlags(0)); + g_once_init_leave(&type_volatile, type); + } + + return type_volatile; +} + +static gboolean webkit_accessible_action_do_action(AtkAction* action, gint i) +{ + g_return_val_if_fail(i == 0, FALSE); + return core(action)->performDefaultAction(); +} + +static gint webkit_accessible_action_get_n_actions(AtkAction* action) +{ + return 1; +} + +static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i) +{ + g_return_val_if_fail(i == 0, 0); + // TODO: Need a way to provide/localize action descriptions. + notImplemented(); + return ""; +} + +static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i) +{ + 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, 0); + return returnString(core(action)->actionVerb()); +} + +static void atk_action_interface_init(AtkActionIface* iface) +{ + iface->do_action = webkit_accessible_action_do_action; + iface->get_n_actions = webkit_accessible_action_get_n_actions; + iface->get_description = webkit_accessible_action_get_description; + iface->get_keybinding = webkit_accessible_action_get_keybinding; + iface->get_name = webkit_accessible_action_get_name; +} + +// Selection (for controls) + +static AccessibilityObject* listObjectForSelection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + + // Only list boxes and menu lists supported so far. + if (!coreSelection->isListBox() && !coreSelection->isMenuList()) + return 0; + + // For list boxes the list object is just itself. + if (coreSelection->isListBox()) + return coreSelection; + + // For menu lists we need to return the first accessible child, + // with role MenuListPopupRole, since that's the one holding the list + // of items with role MenuListOptionRole. + AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children(); + if (!children.size()) + return 0; + + AccessibilityObject* listObject = children.at(0).get(); + if (!listObject->isMenuListPopup()) + return 0; + + return listObject; +} + +static AccessibilityObject* optionFromList(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || i < 0) + return 0; + + // Need to select the proper list object depending on the type. + AccessibilityObject* listObject = listObjectForSelection(selection); + if (!listObject) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector options = listObject->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 || !coreSelection->isAccessibilityRenderObject() || i < 0) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + if (coreSelection->isListBox()) + coreSelection->selectedChildren(selectedItems); + else if (coreSelection->isMenuList()) { + RenderObject* renderer = toAccessibilityRenderObject(coreSelection)->renderer(); + if (!renderer) + return 0; + + SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node())); + int selectedIndex = selectNode->selectedIndex(); + const Vector<Element*> listItems = selectNode->listItems(); + + if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size())) + return 0; + + return optionFromList(selection, selectedIndex); + } + + 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* coreSelection = core(selection); + if (!coreSelection) + return false; + + AccessibilityObject* option = optionFromList(selection, i); + if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) { + option->setSelected(true); + return option->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() || coreSelection->isMenuList()) { + // 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->isAccessibilityRenderObject()) + return 0; + + if (coreSelection->isListBox()) { + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + coreSelection->selectedChildren(selectedItems); + return static_cast<gint>(selectedItems.size()); + } + + if (coreSelection->isMenuList()) { + RenderObject* renderer = toAccessibilityRenderObject(coreSelection)->renderer(); + if (!renderer) + return 0; + + SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node())); + int selectedIndex = selectNode->selectedIndex(); + const Vector<Element*> listItems = selectNode->listItems(); + + return selectedIndex >= 0 && selectedIndex < static_cast<int>(listItems.size()); + } + + return 0; +} + +static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return 0; + + AccessibilityObject* option = optionFromList(selection, i); + if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) + return option->isSelected(); + + return false; +} + +static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return 0; + + // TODO: This is only getting called if i == 0. What is preventing the rest? + AccessibilityObject* option = optionFromSelection(selection, i); + if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) { + option->setSelected(false); + return !option->isSelected(); + } + + return false; +} + +static gboolean webkit_accessible_selection_select_all_selection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || !coreSelection->isMultiSelectable()) + 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* utf8Substr(const gchar* string, gint start, gint end) +{ + ASSERT(string); + glong strLen = g_utf8_strlen(string, -1); + if (start > strLen || end > strLen) + return 0; + gchar* startPtr = g_utf8_offset_to_pointer(string, start); + gsize lenInBytes = g_utf8_offset_to_pointer(string, end) - startPtr + 1; + gchar* output = static_cast<gchar*>(g_malloc0(lenInBytes + 1)); + return g_utf8_strncpy(output, startPtr, end - start + 1); +} + +// This function is not completely general, is it's tied to the +// internals of WebCore's text presentation. +static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to) +{ + CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables); + gchar* utf8String = utf8Substr(stringUTF8.data(), from, to); + if (!g_utf8_validate(utf8String, -1, 0)) { + g_free(utf8String); + return 0; + } + gsize len = strlen(utf8String); + 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(ptr, len, &index, &start); + g_string_append_len(ret, ptr, index); + if (index == start) + break; + g_string_append_c(ret, ' '); + ptr += start; + len -= start; + } + + g_free(utf8String); + return g_string_free(ret, FALSE); +} + +gchar* textForRenderer(RenderObject* renderer) +{ + GString* resultText = g_string_new(0); + + if (!renderer) + return g_string_free(resultText, FALSE); + + // For RenderBlocks, piece together the text from the RenderText objects they contain. + for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) { + if (object->isBR()) { + g_string_append(resultText, "\n"); + continue; + } + + RenderText* renderText; + if (object->isText()) + renderText = toRenderText(object); + else { + // We need to check children, if any, to consider when + // current object is not a text object but some of its + // children are, in order not to miss those portions of + // text by not properly handling those situations + if (object->firstChild()) + g_string_append(resultText, textForRenderer(object)); + + continue; + } + + InlineTextBox* box = renderText->firstTextBox(); + while (box) { + gchar* text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end()); + g_string_append(resultText, 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 the next sibling is a BR, we'll add the newline when we examine that child. + if (!box->nextOnLineExists() && (!object->nextSibling() || !object->nextSibling()->isBR())) + g_string_append(resultText, "\n"); + box = box->nextTextBox(); + } + } + + // Insert the text of the marker for list item in the right place, if present + if (renderer->isListItem()) { + String markerText = toRenderListItem(renderer)->markerTextWithSuffix(); + if (renderer->style()->direction() == LTR) + g_string_prepend(resultText, markerText.utf8().data()); + else + g_string_append(resultText, markerText.utf8().data()); + } + + return g_string_free(resultText, FALSE); +} + +gchar* textForObject(AccessibilityRenderObject* accObject) +{ + GString* str = g_string_new(0); + + // For text controls, we can get the text line by line. + 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 if (accObject->isAccessibilityRenderObject()) { + GOwnPtr<gchar> rendererText(textForRenderer(accObject->renderer())); + g_string_append(str, rendererText.get()); + } + + return g_string_free(str, FALSE); +} + +static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset) +{ + AccessibilityObject* coreObject = core(text); + String ret; + unsigned start = startOffset; + if (endOffset == -1) { + endOffset = coreObject->stringValue().length(); + if (!endOffset) + endOffset = coreObject->textUnderElement().length(); + } + int length = endOffset - startOffset; + + if (coreObject->isTextControl()) + ret = coreObject->doAXStringForRange(PlainTextRange(start, length)); + else { + ret = coreObject->stringValue().substring(start, length); + if (!ret) + ret = coreObject->textUnderElement().substring(start, length); + } + + if (!ret.length()) { + // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs) + ret = String(textForObject(static_cast<AccessibilityRenderObject*>(coreObject))); + if (!endOffset) + endOffset = ret.length(); + ret = ret.substring(start, endOffset - startOffset); + } + + // Prefix a item number/bullet if needed + if (coreObject->roleValue() == ListItemRole) { + RenderObject* objRenderer = static_cast<AccessibilityRenderObject*>(coreObject)->renderer(); + if (objRenderer && objRenderer->isListItem()) { + String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix(); + ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText; + } + } + + return g_strdup(ret.utf8().data()); +} + +static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject) +{ + gpointer data = g_object_get_data(G_OBJECT(textObject), "webkit-accessible-gail-text-util"); + if (data) + return static_cast<GailTextUtil*>(data); + + GailTextUtil* gailTextUtil = gail_text_util_new(); + gail_text_util_text_setup(gailTextUtil, webkit_accessible_text_get_text(textObject, 0, -1)); + g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-gail-text-util", gailTextUtil, g_object_unref); + return gailTextUtil; +} + +static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) +{ + AccessibilityObject* coreObject = core(textObject); + + HostWindow* hostWindow = coreObject->document()->view()->hostWindow(); + if (!hostWindow) + return 0; + PlatformPageClient webView = hostWindow->platformPageClient(); + if (!webView) + return 0; + + AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); + if (!accObject) + return 0; + + // Create a string with the layout as it appears on the screen + PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(accObject)); + g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref); + return layout; +} + +static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) +{ + return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset); +} + +static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) +{ + return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset); +} + +static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) +{ + return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset); +} + +static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset) +{ + notImplemented(); + return 0; +} + +static gint webkit_accessible_text_get_caret_offset(AtkText* text) +{ + // coreObject is the unignored object whose offset the caller is requesting. + // focusedObject is the object with the caret. It is likely ignored -- unless it's a link. + AccessibilityObject* coreObject = core(text); + Node* focusedNode = coreObject->selection().end().node(); + + if (!focusedNode) + return 0; + + RenderObject* focusedRenderer = focusedNode->renderer(); + AccessibilityObject* focusedObject = coreObject->document()->axObjectCache()->getOrCreate(focusedRenderer); + + int offset; + // Don't ignore links if the offset is being requested for a link. + if (!objectAndOffsetUnignored(focusedObject, offset, !coreObject->isLink())) + return 0; + + // TODO: Verify this for RTL text. + return offset; +} + +static int baselinePositionForAccessibilityRenderObject(RenderObject* renderObject) +{ + // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was + // removed in r70072. The implementation looks incorrect though, because this is not the + // baseline of the underlying RenderObject, but of the AccessibilityRenderObject. + const Font& f = renderObject->firstLineStyle()->font(); + return f.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - f.height()) / 2; +} + +static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object) +{ + if (!object->isAccessibilityRenderObject()) + return 0; + + RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(object)->renderer(); + RenderStyle* style = renderer->style(); + + AtkAttributeSet* result = 0; + GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize())); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get()); + + Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor); + if (bgColor.isValid()) { + buffer.set(g_strdup_printf("%i,%i,%i", + bgColor.red(), bgColor.green(), bgColor.blue())); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get()); + } + + Color fgColor = style->visitedDependentColor(CSSPropertyColor); + if (fgColor.isValid()) { + buffer.set(g_strdup_printf("%i,%i,%i", + fgColor.red(), fgColor.green(), fgColor.blue())); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get()); + } + + int baselinePosition; + bool includeRise = true; + switch (style->verticalAlign()) { + case SUB: + baselinePosition = -1 * baselinePositionForAccessibilityRenderObject(renderer); + break; + case SUPER: + baselinePosition = baselinePositionForAccessibilityRenderObject(renderer); + break; + case BASELINE: + baselinePosition = 0; + break; + default: + includeRise = false; + break; + } + + if (includeRise) { + buffer.set(g_strdup_printf("%i", baselinePosition)); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get()); + } + + int indentation = style->textIndent().calcValue(object->size().width()); + if (indentation != undefinedLength) { + buffer.set(g_strdup_printf("%i", indentation)); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get()); + } + + String fontFamilyName = style->font().family().family().string(); + if (fontFamilyName.left(8) == "-webkit-") + fontFamilyName = fontFamilyName.substring(8); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data()); + + int fontWeight = -1; + switch (style->font().weight()) { + case FontWeight100: + fontWeight = 100; + break; + case FontWeight200: + fontWeight = 200; + break; + case FontWeight300: + fontWeight = 300; + break; + case FontWeight400: + fontWeight = 400; + break; + case FontWeight500: + fontWeight = 500; + break; + case FontWeight600: + fontWeight = 600; + break; + case FontWeight700: + fontWeight = 700; + break; + case FontWeight800: + fontWeight = 800; + break; + case FontWeight900: + fontWeight = 900; + } + if (fontWeight > 0) { + buffer.set(g_strdup_printf("%i", fontWeight)); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get()); + } + + switch (style->textAlign()) { + case TAAUTO: + break; + case LEFT: + case WEBKIT_LEFT: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left"); + break; + case RIGHT: + case WEBKIT_RIGHT: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right"); + break; + case CENTER: + case WEBKIT_CENTER: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center"); + break; + case JUSTIFY: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill"); + } + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true"); + + return result; +} + +static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b) +{ + return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value); +} + +// Returns an AtkAttributeSet with the elements of a1 which are either +// not present or different in a2. Neither a1 nor a2 should be used +// after calling this function. +static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* a1, AtkAttributeSet* a2) +{ + if (!a2) + return a1; + + AtkAttributeSet* i = a1; + AtkAttributeSet* found; + AtkAttributeSet* toDelete = 0; + + while (i) { + found = g_slist_find_custom(a2, i->data, (GCompareFunc)compareAttribute); + if (found) { + AtkAttributeSet* t = i->next; + toDelete = g_slist_prepend(toDelete, i->data); + a1 = g_slist_delete_link(a1, i); + i = t; + } else + i = i->next; + } + + atk_attribute_set_free(a2); + atk_attribute_set_free(toDelete); + return a1; +} + +static guint accessibilityObjectLength(const AccessibilityObject* object) +{ + // Non render objects are not taken into account + if (!object->isAccessibilityRenderObject()) + return 0; + + // For those objects implementing the AtkText interface we use the + // well known API to always get the text in a consistent way + AtkObject* atkObj = ATK_OBJECT(object->wrapper()); + if (ATK_IS_TEXT(atkObj)) { + GOwnPtr<gchar> text(webkit_accessible_text_get_text(ATK_TEXT(atkObj), 0, -1)); + return g_utf8_strlen(text.get(), -1); + } + + // Even if we don't expose list markers to Assistive + // Technologies, we need to have a way to measure their length + // for those cases when it's needed to take it into account + // separately (as in getAccessibilityObjectForOffset) + RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(object)->renderer(); + if (renderer && renderer->isListMarker()) { + RenderListMarker* marker = toRenderListMarker(renderer); + return marker->text().length() + marker->suffix().length(); + } + + return 0; +} + +static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset) +{ + const AccessibilityObject* result; + guint length = accessibilityObjectLength(object); + if (length > offset) { + *startOffset = 0; + *endOffset = length; + result = object; + } else { + *startOffset = -1; + *endOffset = -1; + result = 0; + } + + if (!object->firstChild()) + return result; + + AccessibilityObject* child = object->firstChild(); + guint currentOffset = 0; + guint childPosition = 0; + while (child && currentOffset <= offset) { + guint childLength = accessibilityObjectLength(child); + currentOffset = childLength + childPosition; + if (currentOffset > offset) { + gint childStartOffset; + gint childEndOffset; + const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition, &childStartOffset, &childEndOffset); + if (childStartOffset >= 0) { + *startOffset = childStartOffset + childPosition; + *endOffset = childEndOffset + childPosition; + result = grandChild; + } + } else { + childPosition += childLength; + child = child->nextSibling(); + } + } + return result; +} + +static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset) +{ + const AccessibilityObject *child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset); + if (!child) { + *startOffset = -1; + *endOffset = -1; + return 0; + } + + AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element); + AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child); + + return attributeSetDifference(childAttributes, defaultAttributes); +} + +static AtkAttributeSet* webkit_accessible_text_get_run_attributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset) +{ + AccessibilityObject* coreObject = core(text); + AtkAttributeSet* result; + + if (!coreObject) { + *startOffset = 0; + *endOffset = atk_text_get_character_count(text); + return 0; + } + + if (offset == -1) + offset = atk_text_get_caret_offset(text); + + result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset); + + if (*startOffset < 0) { + *startOffset = offset; + *endOffset = offset; + } + + return result; +} + +static AtkAttributeSet* webkit_accessible_text_get_default_attributes(AtkText* text) +{ + AccessibilityObject* coreObject = core(text); + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return 0; + + return getAttributeSetForAccessibilityObject(coreObject); +} + +static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords) +{ + gchar* textContent = webkit_accessible_text_get_text(text, startOffset, -1); + gint textLength = g_utf8_strlen(textContent, -1); + + // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps. + gint rangeLength = length; + if (rangeLength < 0 || rangeLength > textLength) + rangeLength = textLength; + AccessibilityObject* coreObject = core(text); + + IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength)); + switch(coords) { + case ATK_XY_SCREEN: + extents = coreObject->document()->view()->contentsToScreen(extents); + break; + case ATK_XY_WINDOW: + // No-op + break; + } + + return extents; +} + +static void webkit_accessible_text_get_character_extents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords) +{ + IntRect extents = textExtents(text, offset, 1, coords); + *x = extents.x(); + *y = extents.y(); + *width = extents.width(); + *height = extents.height(); +} + +static void webkit_accessible_text_get_range_extents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect) +{ + IntRect extents = textExtents(text, startOffset, endOffset - startOffset + 1, coords); + rect->x = extents.x(); + rect->y = extents.y(); + rect->width = extents.width(); + rect->height = extents.height(); +} + +static gint webkit_accessible_text_get_character_count(AtkText* text) +{ + return accessibilityObjectLength(core(text)); +} + +static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gint y, AtkCoordType coords) +{ + // FIXME: Use the AtkCoordType + // TODO: Is it correct to ignore range.length? + IntPoint pos(x, y); + PlainTextRange range = core(text)->doAXRangeForPosition(pos); + return range.start; +} + +static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset) +{ + if (!coreObject->isAccessibilityRenderObject()) + return; + + // Early return if the selection doesn't affect the selected node. + if (!selectionBelongsToObject(coreObject, selection)) + return; + + // We need to find the exact start and end positions in the + // selected node that intersects the selection, to later on get + // the right values for the effective start and end offsets. + ExceptionCode ec = 0; + Position nodeRangeStart; + Position nodeRangeEnd; + Node* node = coreObject->node(); + RefPtr<Range> selRange = selection.toNormalizedRange(); + + // If the selection affects the selected node and its first + // possible position is also in the selection, we must set + // nodeRangeStart to that position, otherwise to the selection's + // start position (it would belong to the node anyway). + Node* firstLeafNode = node->firstDescendant(); + if (selRange->isPointInRange(firstLeafNode, 0, ec)) + nodeRangeStart = firstPositionInNode(firstLeafNode); + else + nodeRangeStart = selRange->startPosition(); + + // If the selection affects the selected node and its last + // possible position is also in the selection, we must set + // nodeRangeEnd to that position, otherwise to the selection's + // end position (it would belong to the node anyway). + Node* lastLeafNode = node->lastDescendant(); + if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), ec)) + nodeRangeEnd = lastPositionInNode(lastLeafNode); + else + nodeRangeEnd = selRange->endPosition(); + + // Calculate position of the selected range inside the object. + Position parentFirstPosition = firstPositionInNode(node); + RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart); + + // Set values for start and end offsets. + startOffset = TextIterator::rangeLength(rangeInParent.get()); + RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd); + endOffset = startOffset + TextIterator::rangeLength(nodeRange.get()); +} + +static gint webkit_accessible_text_get_n_selections(AtkText* text) +{ + AccessibilityObject* coreObject = core(text); + VisibleSelection selection = coreObject->selection(); + + // Only range selections are needed for the purpose of this method + if (!selection.isRange()) + return 0; + + // We don't support multiple selections for now, so there's only + // two possibilities + // Also, we don't want to do anything if the selection does not + // belong to the currently selected object. We have to check since + // there's no way to get the selection for a given object, only + // the global one (the API is a bit confusing) + return selectionBelongsToObject(coreObject, selection) ? 1 : 0; +} + +static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset) +{ + // Default values, unless the contrary is proved + *startOffset = *endOffset = 0; + + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. + if (selectionNum) + return 0; + + // Get the offsets of the selection for the selected object + AccessibilityObject* coreObject = core(text); + VisibleSelection selection = coreObject->selection(); + getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset); + + // Return 0 instead of "", as that's the expected result for + // this AtkText method when there's no selection + if (*startOffset == *endOffset) + return 0; + + return webkit_accessible_text_get_text(text, *startOffset, *endOffset); +} + +static gboolean webkit_accessible_text_add_selection(AtkText* text, gint start_offset, gint end_offset) +{ + notImplemented(); + return FALSE; +} + +static gboolean webkit_accessible_text_set_selection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset) +{ + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. + if (selectionNum) + return FALSE; + + // Consider -1 and out-of-bound values and correct them to length + gint textCount = webkit_accessible_text_get_character_count(text); + if (startOffset < 0 || startOffset > textCount) + startOffset = textCount; + if (endOffset < 0 || endOffset > textCount) + endOffset = textCount; + + AccessibilityObject* coreObject = core(text); + PlainTextRange textRange(startOffset, endOffset - startOffset); + VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange); + coreObject->setSelectedVisiblePositionRange(range); + + return TRUE; +} + +static gboolean webkit_accessible_text_remove_selection(AtkText* text, gint selectionNum) +{ + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. + if (selectionNum) + return FALSE; + + // Do nothing if current selection doesn't belong to the object + if (!webkit_accessible_text_get_n_selections(text)) + return FALSE; + + // Set a new 0-sized selection to the caret position, in order + // to simulate selection removal (GAIL style) + gint caretOffset = webkit_accessible_text_get_caret_offset(text); + return webkit_accessible_text_set_selection(text, selectionNum, caretOffset, caretOffset); +} + +static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset) +{ + AccessibilityObject* coreObject = core(text); + + PlainTextRange textRange(offset, 0); + VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange); + coreObject->setSelectedVisiblePositionRange(range); + + return TRUE; +} + +static void atk_text_interface_init(AtkTextIface* iface) +{ + iface->get_text = webkit_accessible_text_get_text; + iface->get_text_after_offset = webkit_accessible_text_get_text_after_offset; + iface->get_text_at_offset = webkit_accessible_text_get_text_at_offset; + iface->get_character_at_offset = webkit_accessible_text_get_character_at_offset; + iface->get_text_before_offset = webkit_accessible_text_get_text_before_offset; + iface->get_caret_offset = webkit_accessible_text_get_caret_offset; + iface->get_run_attributes = webkit_accessible_text_get_run_attributes; + iface->get_default_attributes = webkit_accessible_text_get_default_attributes; + iface->get_character_extents = webkit_accessible_text_get_character_extents; + iface->get_range_extents = webkit_accessible_text_get_range_extents; + iface->get_character_count = webkit_accessible_text_get_character_count; + iface->get_offset_at_point = webkit_accessible_text_get_offset_at_point; + iface->get_n_selections = webkit_accessible_text_get_n_selections; + iface->get_selection = webkit_accessible_text_get_selection; + + // set methods + iface->add_selection = webkit_accessible_text_add_selection; + iface->remove_selection = webkit_accessible_text_remove_selection; + iface->set_selection = webkit_accessible_text_set_selection; + iface->set_caret_offset = webkit_accessible_text_set_caret_offset; +} + +// EditableText + +static gboolean webkit_accessible_editable_text_set_run_attributes(AtkEditableText* text, AtkAttributeSet* attrib_set, gint start_offset, gint end_offset) +{ + notImplemented(); + return FALSE; +} + +static void webkit_accessible_editable_text_set_text_contents(AtkEditableText* text, const gchar* string) +{ + // FIXME: string nullcheck? + core(text)->setValue(String::fromUTF8(string)); +} + +static void webkit_accessible_editable_text_insert_text(AtkEditableText* text, const gchar* string, gint length, gint* position) +{ + // FIXME: string nullcheck? + + AccessibilityObject* coreObject = core(text); + // FIXME: Not implemented in WebCore + //coreObject->setSelectedTextRange(PlainTextRange(*position, 0)); + //coreObject->setSelectedText(String::fromUTF8(string)); + + if (!coreObject->document() || !coreObject->document()->frame()) + return; + coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(*position, 0))); + coreObject->setFocused(true); + // FIXME: We should set position to the actual inserted text length, which may be less than that requested. + if (coreObject->document()->frame()->editor()->insertTextWithoutSendingTextEvent(String::fromUTF8(string), false, 0)) + *position += length; +} + +static void webkit_accessible_editable_text_copy_text(AtkEditableText* text, gint start_pos, gint end_pos) +{ + notImplemented(); +} + +static void webkit_accessible_editable_text_cut_text(AtkEditableText* text, gint start_pos, gint end_pos) +{ + notImplemented(); +} + +static void webkit_accessible_editable_text_delete_text(AtkEditableText* text, gint start_pos, gint end_pos) +{ + AccessibilityObject* coreObject = core(text); + // FIXME: Not implemented in WebCore + //coreObject->setSelectedTextRange(PlainTextRange(start_pos, end_pos - start_pos)); + //coreObject->setSelectedText(String()); + + if (!coreObject->document() || !coreObject->document()->frame()) + return; + coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos))); + coreObject->setFocused(true); + coreObject->document()->frame()->editor()->performDelete(); +} + +static void webkit_accessible_editable_text_paste_text(AtkEditableText* text, gint position) +{ + notImplemented(); +} + +static void atk_editable_text_interface_init(AtkEditableTextIface* iface) +{ + iface->set_run_attributes = webkit_accessible_editable_text_set_run_attributes; + iface->set_text_contents = webkit_accessible_editable_text_set_text_contents; + iface->insert_text = webkit_accessible_editable_text_insert_text; + iface->copy_text = webkit_accessible_editable_text_copy_text; + iface->cut_text = webkit_accessible_editable_text_cut_text; + iface->delete_text = webkit_accessible_editable_text_delete_text; + iface->paste_text = webkit_accessible_editable_text_paste_text; +} + +static void contentsToAtk(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width = 0, gint* height = 0) +{ + FrameView* frameView = coreObject->documentFrameView(); + + if (frameView) { + switch (coordType) { + case ATK_XY_WINDOW: + rect = frameView->contentsToWindow(rect); + break; + case ATK_XY_SCREEN: + rect = frameView->contentsToScreen(rect); + break; + } + } + + if (x) + *x = rect.x(); + if (y) + *y = rect.y(); + if (width) + *width = rect.width(); + if (height) + *height = rect.height(); +} + +static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coordType, gint x, gint y) +{ + IntPoint pos(x, y); + + FrameView* frameView = coreObject->documentFrameView(); + if (frameView) { + switch (coordType) { + case ATK_XY_SCREEN: + return frameView->screenToContents(pos); + case ATK_XY_WINDOW: + return frameView->windowToContents(pos); + } + } + + return pos; +} + +static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkComponent* component, gint x, gint y, AtkCoordType coordType) +{ + IntPoint pos = atkToContents(core(component), coordType, x, y); + + AccessibilityObject* target = core(component)->accessibilityHitTest(pos); + if (!target) + return 0; + g_object_ref(target->wrapper()); + return target->wrapper(); +} + +static void webkit_accessible_component_get_extents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType) +{ + IntRect rect = core(component)->elementRect(); + contentsToAtk(core(component), coordType, rect, x, y, width, height); +} + +static gboolean webkit_accessible_component_grab_focus(AtkComponent* component) +{ + core(component)->setFocused(true); + return core(component)->isFocused(); +} + +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; + iface->grab_focus = webkit_accessible_component_grab_focus; +} + +// Image + +static void webkit_accessible_image_get_image_position(AtkImage* image, gint* x, gint* y, AtkCoordType coordType) +{ + IntRect rect = core(image)->elementRect(); + contentsToAtk(core(image), coordType, rect, x, y); +} + +static const gchar* webkit_accessible_image_get_image_description(AtkImage* image) +{ + return returnString(core(image)->accessibilityDescription()); +} + +static void webkit_accessible_image_get_image_size(AtkImage* image, gint* width, gint* height) +{ + IntSize size = core(image)->size(); + + if (width) + *width = size.width(); + if (height) + *height = size.height(); +} + +static void atk_image_interface_init(AtkImageIface* iface) +{ + iface->get_image_position = webkit_accessible_image_get_image_position; + iface->get_image_description = webkit_accessible_image_get_image_description; + iface->get_image_size = webkit_accessible_image_get_image_size; +} + +// Table + +static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) + return static_cast<AccessibilityTable*>(accTable)->cellForColumnAndRow(column, row); + return 0; +} + +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); + AccessibilityObject::AccessibilityChildrenVector::iterator position; + 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) + return 0; + 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); +} + +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) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) + return static_cast<AccessibilityTable*>(accTable)->columnCount(); + return 0; +} + +static gint webkit_accessible_table_get_n_rows(AtkTable* table) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) + return static_cast<AccessibilityTable*>(accTable)->rowCount(); + return 0; +} + +static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint row, gint column) +{ + AccessibilityTableCell* axCell = cell(table, row, column); + if (axCell) { + pair<int, int> columnRange; + axCell->columnIndexRange(columnRange); + return columnRange.second; + } + return 0; +} + +static gint webkit_accessible_table_get_row_extent_at(AtkTable* table, gint row, gint column) +{ + AccessibilityTableCell* axCell = cell(table, row, column); + if (axCell) { + pair<int, int> rowRange; + axCell->rowIndexRange(rowRange); + return rowRange.second; + } + return 0; +} + +static AtkObject* webkit_accessible_table_get_column_header(AtkTable* table, gint column) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + AccessibilityObject::AccessibilityChildrenVector allColumnHeaders; + static_cast<AccessibilityTable*>(accTable)->columnHeaders(allColumnHeaders); + unsigned columnCount = allColumnHeaders.size(); + for (unsigned k = 0; k < columnCount; ++k) { + pair<int, int> columnRange; + AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allColumnHeaders.at(k).get()); + cell->columnIndexRange(columnRange); + if (columnRange.first <= column && column < columnRange.first + columnRange.second) + return allColumnHeaders[k]->wrapper(); + } + } + return 0; +} + +static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + AccessibilityObject::AccessibilityChildrenVector allRowHeaders; + static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders); + unsigned rowCount = allRowHeaders.size(); + for (unsigned k = 0; k < rowCount; ++k) { + pair<int, int> rowRange; + AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allRowHeaders.at(k).get()); + cell->rowIndexRange(rowRange); + if (rowRange.first <= row && row < rowRange.first + rowRange.second) + return allRowHeaders[k]->wrapper(); + } + } + 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 && ATK_IS_TEXT(columnHeader)) + return webkit_accessible_text_get_text(ATK_TEXT(columnHeader), 0, -1); + + 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 && ATK_IS_TEXT(rowHeader)) + return webkit_accessible_text_get_text(ATK_TEXT(rowHeader), 0, -1); + + 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 AtkHyperlink* webkitAccessibleHypertextGetLink(AtkHypertext* hypertext, gint index) +{ + AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children(); + if (index < 0 || static_cast<unsigned>(index) >= children.size()) + return 0; + + gint currentLink = -1; + for (unsigned i = 0; i < children.size(); i++) { + AccessibilityObject* coreChild = children.at(i).get(); + if (!coreChild->accessibilityIsIgnored() && coreChild->isLink()) { + currentLink++; + if (index != currentLink) + continue; + + AtkObject* axObject = coreChild->wrapper(); + if (!axObject || !ATK_IS_HYPERLINK_IMPL(axObject)) + return 0; + + return atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(axObject)); + } + } + + return 0; +} + +static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext) +{ + AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children(); + if (!children.size()) + return 0; + + gint linksFound = 0; + for (size_t i = 0; i < children.size(); i++) { + AccessibilityObject* coreChild = children.at(i).get(); + if (!coreChild->accessibilityIsIgnored() && coreChild->isLink()) + linksFound++; + } + + return linksFound; +} + +static gint webkitAccessibleHypertextGetLinkIndex(AtkHypertext* hypertext, gint charIndex) +{ + size_t linksCount = webkitAccessibleHypertextGetNLinks(hypertext); + if (!linksCount) + return -1; + + for (size_t i = 0; i < linksCount; i++) { + AtkHyperlink* hyperlink = ATK_HYPERLINK(webkitAccessibleHypertextGetLink(hypertext, i)); + gint startIndex = atk_hyperlink_get_start_index(hyperlink); + gint endIndex = atk_hyperlink_get_end_index(hyperlink); + + // Check if the char index in the link's offset range + if (startIndex <= charIndex && charIndex < endIndex) + return i; + } + + // Not found if reached + return -1; +} + +static void atkHypertextInterfaceInit(AtkHypertextIface* iface) +{ + iface->get_link = webkitAccessibleHypertextGetLink; + iface->get_n_links = webkitAccessibleHypertextGetNLinks; + iface->get_link_index = webkitAccessibleHypertextGetLinkIndex; +} + +static AtkHyperlink* webkitAccessibleHyperlinkImplGetHyperlink(AtkHyperlinkImpl* hyperlink) +{ + AtkHyperlink* hyperlinkObject = ATK_HYPERLINK(g_object_get_data(G_OBJECT(hyperlink), "hyperlink-object")); + if (!hyperlinkObject) { + hyperlinkObject = ATK_HYPERLINK(webkitAccessibleHyperlinkNew(hyperlink)); + g_object_set_data(G_OBJECT(hyperlink), "hyperlink-object", hyperlinkObject); + } + return hyperlinkObject; +} + +static void atkHyperlinkImplInterfaceInit(AtkHyperlinkImplIface* iface) +{ + iface->get_hyperlink = webkitAccessibleHyperlinkImplGetHyperlink; +} + +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 = 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) 0, 0}, + {(GInterfaceInitFunc)atk_selection_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_editable_text_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_text_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_component_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_image_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_table_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atkHypertextInterfaceInit, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atkHyperlinkImplInterfaceInit, + (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_HYPERTEXT, + WAI_HYPERLINK, + WAI_DOCUMENT +}; + +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: + return ATK_TYPE_TEXT; + case WAI_COMPONENT: + return ATK_TYPE_COMPONENT; + case WAI_IMAGE: + return ATK_TYPE_IMAGE; + case WAI_TABLE: + return ATK_TYPE_TABLE; + case WAI_HYPERTEXT: + return ATK_TYPE_HYPERTEXT; + case WAI_HYPERLINK: + return ATK_TYPE_HYPERLINK_IMPL; + case WAI_DOCUMENT: + return ATK_TYPE_DOCUMENT; + } + + return G_TYPE_INVALID; +} + +static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) +{ + guint16 interfaceMask = 0; + + // Component interface is always supported + interfaceMask |= 1 << WAI_COMPONENT; + + AccessibilityRole role = coreObject->roleValue(); + + // Action + // As the implementation of the AtkAction interface is a very + // basic one (just relays in executing the default action for each + // object, and only supports having one action per object), it is + // better just to implement this interface for every instance of + // the WebKitAccessible class and let WebCore decide what to do. + interfaceMask |= 1 << WAI_ACTION; + + // Hyperlink + if (coreObject->isLink()) + interfaceMask |= 1 << WAI_HYPERLINK; + + // Selection + if (coreObject->isListBox() || coreObject->isMenuList()) + interfaceMask |= 1 << WAI_SELECTION; + + // Text & Editable Text + if (role == StaticTextRole || coreObject->isMenuListOption()) + interfaceMask |= 1 << WAI_TEXT; + else if (coreObject->isAccessibilityRenderObject()) { + if (coreObject->isTextControl()) { + interfaceMask |= 1 << WAI_TEXT; + if (!coreObject->isReadOnly()) + interfaceMask |= 1 << WAI_EDITABLE_TEXT; + } else { + AccessibilityRenderObject* axRenderObject = static_cast<AccessibilityRenderObject*>(coreObject); + RenderObject* renderer = axRenderObject->renderer(); + if (role != TableRole) { + interfaceMask |= 1 << WAI_HYPERTEXT; + if (renderer && renderer->childrenInline()) + interfaceMask |= 1 << WAI_TEXT; + } + + // Add the TEXT interface for list items whose + // first accessible child has a text renderer + if (role == ListItemRole) { + AccessibilityObject::AccessibilityChildrenVector children = axRenderObject->children(); + if (children.size()) { + AccessibilityObject* axRenderChild = children.at(0).get(); + interfaceMask |= getInterfaceMaskFromObject(axRenderChild); + } + } + } + } + + // Image + if (coreObject->isImage()) + interfaceMask |= 1 << WAI_IMAGE; + + // Table + if (role == TableRole) + interfaceMask |= 1 << WAI_TABLE; + + // Document + if (role == WebAreaRole) + interfaceMask |= 1 << WAI_DOCUMENT; + + return interfaceMask; +} + +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; +} + +static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject) +{ + static const GTypeInfo typeInfo = { + sizeof(WebKitAccessibleClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) 0, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(WebKitAccessible), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) 0, + 0 /* value table */ + }; + + guint16 interfaceMask = getInterfaceMaskFromObject(coreObject); + const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask); + GType type = g_type_from_name(atkTypeName); + if (type) + return type; + + type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, + atkTypeName, + &typeInfo, GTypeFlags(0)); + for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) { + if (interfaceMask & (1 << i)) + g_type_add_interface_static(type, + GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)), + &AtkInterfacesInitFunctions[i]); + } + + return type; +} + +WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject) +{ + GType type = getAccessibilityTypeFromObject(coreObject); + AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0)); + + atk_object_initialize(object, coreObject); + + return WEBKIT_ACCESSIBLE(object); +} + +AccessibilityObject* webkit_accessible_get_accessibility_object(WebKitAccessible* accessible) +{ + return accessible->m_object; +} + +void webkit_accessible_detach(WebKitAccessible* accessible) +{ + ASSERT(accessible->m_object); + + // We replace the WebCore AccessibilityObject with a fallback object that + // provides default implementations to avoid repetitive null-checking after + // detachment. + accessible->m_object = fallbackObject(); +} + +AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible) +{ + if (!accessible->m_object) + return 0; + + RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement(); + if (!focusedObj) + return 0; + + return focusedObj->wrapper(); +} + +AccessibilityObject* objectAndOffsetUnignored(AccessibilityObject* coreObject, int& offset, bool ignoreLinks) +{ + Node* endNode = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); + int endOffset = coreObject->selection().end().computeOffsetInContainerNode(); + // Indication that something bogus has transpired. + offset = -1; + + AccessibilityObject* realObject = coreObject; + if (realObject->accessibilityIsIgnored()) + realObject = realObject->parentObjectUnignored(); + if (!realObject) + return 0; + + if (ignoreLinks && realObject->isLink()) + realObject = realObject->parentObjectUnignored(); + if (!realObject) + return 0; + + Node* node = static_cast<AccessibilityRenderObject*>(realObject)->renderer()->node(); + if (node) { + RefPtr<Range> range = rangeOfContents(node); + if (range->ownerDocument() == node->document()) { + ExceptionCode ec = 0; + range->setEndBefore(endNode, ec); + if (range->boundaryPointsValid()) + offset = range->text().length() + endOffset; + } + } + return realObject; +} + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h new file mode 100644 index 0000000..e8cec08 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * Copyright (C) 2009 Jan Alonzo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef AccessibilityObjectWrapperAtk_h +#define AccessibilityObjectWrapperAtk_h + +#include <atk/atk.h> + +namespace WebCore { + class AccessibilityObject; +} + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_ACCESSIBLE (webkit_accessible_get_type ()) +#define WEBKIT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessible)) +#define WEBKIT_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessibleClass)) +#define WEBKIT_IS_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_ACCESSIBLE)) +#define WEBKIT_IS_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_ACCESSIBLE)) +#define WEBKIT_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessibleClass)) + +typedef struct _WebKitAccessible WebKitAccessible; +typedef struct _WebKitAccessibleClass WebKitAccessibleClass; + +struct _WebKitAccessible +{ + AtkObject parent; + WebCore::AccessibilityObject* m_object; +}; + +struct _WebKitAccessibleClass +{ + AtkObjectClass parent_class; +}; + +GType webkit_accessible_get_type (void) G_GNUC_CONST; + +WebKitAccessible* webkit_accessible_new (WebCore::AccessibilityObject* core_object); + +WebCore::AccessibilityObject* webkit_accessible_get_accessibility_object (WebKitAccessible* accessible); + +void webkit_accessible_detach (WebKitAccessible* accessible); + +AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible); + +WebCore::AccessibilityObject* objectAndOffsetUnignored(WebCore::AccessibilityObject* coreObject, int& offset, bool ignoreLinks); + +G_END_DECLS + +#endif // AccessibilityObjectWrapperAtk_h diff --git a/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp new file mode 100644 index 0000000..5927430 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2010 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitAccessibleHyperlink.h" + +#if HAVE(ACCESSIBILITY) + +#include "AXObjectCache.h" +#include "AccessibilityObject.h" +#include "AccessibilityObjectWrapperAtk.h" +#include "AccessibilityRenderObject.h" +#include "NotImplemented.h" +#include "Position.h" +#include "Range.h" +#include "RenderListMarker.h" +#include "RenderObject.h" +#include "TextIterator.h" + +#include <atk/atk.h> +#include <glib.h> + +using namespace WebCore; + +struct _WebKitAccessibleHyperlinkPrivate { + WebKitAccessible* hyperlinkImpl; +}; + +#define WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkPrivate)) + +enum { + PROP_0, + + PROP_HYPERLINK_IMPL +}; + +static gpointer webkitAccessibleHyperlinkParentClass = 0; + +// Used to provide const char* returns. +static const char* returnString(const String& str) +{ + static CString returnedString; + returnedString = str.utf8(); + return returnedString.data(); +} + +static AccessibilityObject* core(WebKitAccessible* accessible) +{ + if (!accessible || !WEBKIT_IS_ACCESSIBLE(accessible)) + return 0; + + return webkit_accessible_get_accessibility_object(accessible); +} + +static AccessibilityObject* core(WebKitAccessibleHyperlink* link) +{ + if (!link) + return 0; + + return core(link->priv->hyperlinkImpl); +} + +static AccessibilityObject* core(AtkHyperlink* link) +{ + if (!WEBKIT_IS_ACCESSIBLE_HYPERLINK(link)) + return 0; + + return core(WEBKIT_ACCESSIBLE_HYPERLINK(link)); +} + +static AccessibilityObject* core(AtkAction* action) +{ + return core(WEBKIT_ACCESSIBLE_HYPERLINK(action)); +} + + +static gboolean webkitAccessibleHyperlinkActionDoAction(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), FALSE); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, FALSE); + g_return_val_if_fail(!index, FALSE); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return FALSE; + + AccessibilityObject* coreObject = core(action); + if (!coreObject) + return FALSE; + + return coreObject->performDefaultAction(); +} + +static gint webkitAccessibleHyperlinkActionGetNActions(AtkAction* action) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return 0; + + return 1; +} + +static const gchar* webkitAccessibleHyperlinkActionGetDescription(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + g_return_val_if_fail(!index, 0); + + // TODO: Need a way to provide/localize action descriptions. + notImplemented(); + return ""; +} + +static const gchar* webkitAccessibleHyperlinkActionGetKeybinding(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + g_return_val_if_fail(!index, 0); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return 0; + + AccessibilityObject* coreObject = core(action); + if (!coreObject) + return 0; + + return returnString(coreObject->accessKey().string()); +} + +static const gchar* webkitAccessibleHyperlinkActionGetName(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + g_return_val_if_fail(!index, 0); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return 0; + + AccessibilityObject* coreObject = core(action); + if (!coreObject) + return 0; + + return returnString(coreObject->actionVerb()); +} + +static void atkActionInterfaceInit(AtkActionIface* iface) +{ + iface->do_action = webkitAccessibleHyperlinkActionDoAction; + iface->get_n_actions = webkitAccessibleHyperlinkActionGetNActions; + iface->get_description = webkitAccessibleHyperlinkActionGetDescription; + iface->get_keybinding = webkitAccessibleHyperlinkActionGetKeybinding; + iface->get_name = webkitAccessibleHyperlinkActionGetName; +} + +static gchar* webkitAccessibleHyperlinkGetURI(AtkHyperlink* link, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + // FIXME: Do NOT support more than one instance of an AtkObject + // implementing AtkHyperlinkImpl in every instance of AtkHyperLink + g_return_val_if_fail(!index, 0); + + AccessibilityObject* coreObject = core(link); + if (!coreObject || coreObject->url().isNull()) + return 0; + + return g_strdup(returnString(coreObject->url().string())); +} + +static AtkObject* webkitAccessibleHyperlinkGetObject(AtkHyperlink* link, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + + // FIXME: Do NOT support more than one instance of an AtkObject + // implementing AtkHyperlinkImpl in every instance of AtkHyperLink + g_return_val_if_fail(!index, 0); + + return ATK_OBJECT(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl); +} + +static gint getRangeLengthForObject(AccessibilityObject* obj, Range* range) +{ + // This is going to be the actual length in most of the cases + int baseLength = TextIterator::rangeLength(range); + + // Check whether the current hyperlink belongs to a list item. + // If so, we need to consider the length of the item's marker + AccessibilityObject* parent = obj->parentObjectUnignored(); + if (!parent || !parent->isAccessibilityRenderObject() || !parent->isListItem()) + return baseLength; + + // Even if we don't expose list markers to Assistive + // Technologies, we need to have a way to measure their length + // for those cases when it's needed to take it into account + // separately (as in getAccessibilityObjectForOffset) + AccessibilityObject* markerObj = parent->firstChild(); + if (!markerObj) + return baseLength; + + RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(markerObj)->renderer(); + if (!renderer || !renderer->isListMarker()) + return baseLength; + + RenderListMarker* marker = toRenderListMarker(renderer); + return baseLength + marker->text().length() + marker->suffix().length(); +} + +static gint webkitAccessibleHyperlinkGetStartIndex(AtkHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + + AccessibilityObject* coreObject = core(link); + if (!coreObject) + return 0; + + Node* node = coreObject->node(); + if (!node) + return 0; + + RefPtr<Range> range = Range::create(node->document(), firstPositionInNode(node->parentNode()), firstPositionInNode(node)); + return getRangeLengthForObject(coreObject, range.get()); +} + +static gint webkitAccessibleHyperlinkGetEndIndex(AtkHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + + AccessibilityObject* coreObject = core(link); + if (!coreObject) + return 0; + + Node* node = coreObject->node(); + if (!node) + return 0; + + RefPtr<Range> range = Range::create(node->document(), firstPositionInNode(node->parentNode()), lastPositionInNode(node)); + return getRangeLengthForObject(coreObject, range.get()); +} + +static gboolean webkitAccessibleHyperlinkIsValid(AtkHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); + + // Link is valid for the whole object's lifetime + return TRUE; +} + +static gint webkitAccessibleHyperlinkGetNAnchors(AtkHyperlink* link) +{ + // FIXME Do NOT support more than one instance of an AtkObject + // implementing AtkHyperlinkImpl in every instance of AtkHyperLink + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + return 1; +} + +static gboolean webkitAccessibleHyperlinkIsSelectedLink(AtkHyperlink* link) +{ + // Not implemented: this function is deprecated in ATK now + notImplemented(); + return FALSE; +} + +static void webkitAccessibleHyperlinkGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* pspec) +{ + switch (propId) { + case PROP_HYPERLINK_IMPL: + g_value_set_object(value, WEBKIT_ACCESSIBLE_HYPERLINK(object)->priv->hyperlinkImpl); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + } +} + +static void webkitAccessibleHyperlinkSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) +{ + WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(object)->priv; + + switch (propId) { + case PROP_HYPERLINK_IMPL: + // No need to check and unref previous values of + // priv->hyperlinkImpl as this is a CONSTRUCT ONLY property + priv->hyperlinkImpl = WEBKIT_ACCESSIBLE(g_value_get_object(value)); + g_object_weak_ref(G_OBJECT(priv->hyperlinkImpl), (GWeakNotify)g_object_unref, object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + } +} + +static void webkitAccessibleHyperlinkFinalize(GObject* object) +{ + G_OBJECT_CLASS(webkitAccessibleHyperlinkParentClass)->finalize(object); +} + +static void webkitAccessibleHyperlinkClassInit(AtkHyperlinkClass* klass) +{ + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + + webkitAccessibleHyperlinkParentClass = g_type_class_peek_parent(klass); + + gobjectClass->finalize = webkitAccessibleHyperlinkFinalize; + gobjectClass->set_property = webkitAccessibleHyperlinkSetProperty; + gobjectClass->get_property = webkitAccessibleHyperlinkGetProperty; + + klass->get_uri = webkitAccessibleHyperlinkGetURI; + klass->get_object = webkitAccessibleHyperlinkGetObject; + klass->get_start_index = webkitAccessibleHyperlinkGetStartIndex; + klass->get_end_index = webkitAccessibleHyperlinkGetEndIndex; + klass->is_valid = webkitAccessibleHyperlinkIsValid; + klass->get_n_anchors = webkitAccessibleHyperlinkGetNAnchors; + klass->is_selected_link = webkitAccessibleHyperlinkIsSelectedLink; + + g_object_class_install_property(gobjectClass, PROP_HYPERLINK_IMPL, + g_param_spec_object("hyperlink-impl", + "Hyperlink implementation", + "The associated WebKitAccessible instance.", + WEBKIT_TYPE_ACCESSIBLE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); + + g_type_class_add_private(gobjectClass, sizeof(WebKitAccessibleHyperlinkPrivate)); +} + +static void webkitAccessibleHyperlinkInit(AtkHyperlink* link) +{ + WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv = WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(link); + WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = 0; +} + +GType webkitAccessibleHyperlinkGetType() +{ + static volatile gsize typeVolatile = 0; + + if (g_once_init_enter(&typeVolatile)) { + static const GTypeInfo tinfo = { + sizeof(WebKitAccessibleHyperlinkClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) webkitAccessibleHyperlinkClassInit, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(WebKitAccessibleHyperlink), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) webkitAccessibleHyperlinkInit, + 0 /* value table */ + }; + + static const GInterfaceInfo actionInfo = { + (GInterfaceInitFunc)(GInterfaceInitFunc)atkActionInterfaceInit, + (GInterfaceFinalizeFunc) 0, 0 + }; + + GType type = g_type_register_static(ATK_TYPE_HYPERLINK, "WebKitAccessibleHyperlink", &tinfo, GTypeFlags(0)); + g_type_add_interface_static(type, ATK_TYPE_ACTION, &actionInfo); + + g_once_init_leave(&typeVolatile, type); + } + + return typeVolatile; +} + +WebKitAccessibleHyperlink* webkitAccessibleHyperlinkNew(AtkHyperlinkImpl* hyperlinkImpl) +{ + g_return_val_if_fail(ATK_IS_HYPERLINK_IMPL(hyperlinkImpl), 0); + return WEBKIT_ACCESSIBLE_HYPERLINK(g_object_new(WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, "hyperlink-impl", hyperlinkImpl, 0)); +} + +WebCore::AccessibilityObject* webkitAccessibleHyperlinkGetAccessibilityObject(WebKitAccessibleHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + return core(link); +} + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h new file mode 100644 index 0000000..9df819d --- /dev/null +++ b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef WebKitAccessibleHyperlink_h +#define WebKitAccessibleHyperlink_h + +#include "AccessibilityObjectWrapperAtk.h" + +#include <atk/atk.h> + +namespace WebCore { +class AccessibilityObject; +} + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_ACCESSIBLE_HYPERLINK (webkitAccessibleHyperlinkGetType ()) +#define WEBKIT_ACCESSIBLE_HYPERLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlink)) +#define WEBKIT_ACCESSIBLE_HYPERLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkClass)) +#define WEBKIT_IS_ACCESSIBLE_HYPERLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK)) +#define WEBKIT_IS_ACCESSIBLE_HYPERLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK)) +#define WEBKIT_ACCESSIBLE_HYPERLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkClass)) + +typedef struct _WebKitAccessibleHyperlink WebKitAccessibleHyperlink; +typedef struct _WebKitAccessibleHyperlinkClass WebKitAccessibleHyperlinkClass; +typedef struct _WebKitAccessibleHyperlinkPrivate WebKitAccessibleHyperlinkPrivate; + +struct _WebKitAccessibleHyperlink { + AtkHyperlink parent; + + // private + WebKitAccessibleHyperlinkPrivate *priv; +}; + +struct _WebKitAccessibleHyperlinkClass { + AtkObjectClass parentClass; +}; + +GType webkitAccessibleHyperlinkGetType(void) G_GNUC_CONST; + +WebKitAccessibleHyperlink* webkitAccessibleHyperlinkNew(AtkHyperlinkImpl* hyperlinkImpl); + +WebCore::AccessibilityObject* webkitAccessibleHyperlinkGetAccessibilityObject(WebKitAccessibleHyperlink* link); + +G_END_DECLS + +#endif // WebKitAccessibleHyperlink_h diff --git a/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm b/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm new file mode 100644 index 0000000..13c8ec2 --- /dev/null +++ b/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "AXObjectCache.h" + +#if HAVE(ACCESSIBILITY) + +#import "AccessibilityObject.h" +#import "AccessibilityObjectWrapper.h" +#import "RenderObject.h" +#import "WebCoreViewFactory.h" + +#import <wtf/PassRefPtr.h> + +#ifndef NSAccessibilityLiveRegionChangedNotification +#define NSAccessibilityLiveRegionChangedNotification @"AXLiveRegionChanged" +#endif + +// The simple Cocoa calls in this file don't throw exceptions. + +namespace WebCore { + +void AXObjectCache::detachWrapper(AccessibilityObject* obj) +{ + [obj->wrapper() detach]; + [obj->wrapper() release]; + obj->setWrapper(0); +} + +void AXObjectCache::attachWrapper(AccessibilityObject* obj) +{ + obj->setWrapper([[AccessibilityObjectWrapper alloc] initWithAccessibilityObject:obj]); +} + +void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotification notification) +{ + if (!obj) + return; + + // 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 AXFocusedUIElementChanged: + macNotification = NSAccessibilityFocusedUIElementChangedNotification; + break; + case AXLayoutComplete: + macNotification = "AXLayoutComplete"; + break; + case AXLoadComplete: + macNotification = "AXLoadComplete"; + break; + case AXInvalidStatusChanged: + macNotification = "AXInvalidStatusChanged"; + break; + case AXSelectedChildrenChanged: + macNotification = NSAccessibilitySelectedChildrenChangedNotification; + break; + case AXSelectedTextChanged: + macNotification = NSAccessibilitySelectedTextChangedNotification; + break; + case AXValueChanged: + macNotification = NSAccessibilityValueChangedNotification; + break; + case AXLiveRegionChanged: + macNotification = NSAccessibilityLiveRegionChangedNotification; + break; + case AXRowCountChanged: + macNotification = NSAccessibilityRowCountChangedNotification; + break; +#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + case AXRowExpanded: + macNotification = NSAccessibilityRowExpandedNotification; + break; + case AXRowCollapsed: + macNotification = NSAccessibilityRowCollapsedNotification; + break; +#endif + // Does not exist on Mac. + case AXCheckedStateChanged: + default: + return; + } + + // NSAccessibilityPostNotification will call this method, (but not when running DRT), so ASSERT here to make sure it does not crash. + // https://bugs.webkit.org/show_bug.cgi?id=46662 + ASSERT([obj->wrapper() accessibilityIsIgnored] || true); + + NSAccessibilityPostNotification(obj->wrapper(), macNotification); + + // Used by DRT to know when notifications are posted. + [obj->wrapper() accessibilityPostedNotification:macNotification]; +} + +void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, unsigned) +{ +} + +void AXObjectCache::handleFocusedUIElementChanged(RenderObject*, RenderObject*) +{ + [[WebCoreViewFactory sharedFactory] accessibilityHandleFocusChanged]; +} + +void AXObjectCache::handleScrolledToAnchor(const Node*) +{ +} + +} + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm b/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm new file mode 100644 index 0000000..5c3ee39 --- /dev/null +++ b/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "AccessibilityObject.h" + +#if HAVE(ACCESSIBILITY) + +#import "AccessibilityObjectWrapper.h" +#import "Widget.h" + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + // FrameView attachments are now handled by AccessibilityScrollView, + // so if this is the attachment, it should be ignored. + Widget* widget = 0; + if (isAttachment() && (widget = widgetForAttachmentView()) && widget->isFrameView()) + return true; + + if ([wrapper() attachmentView]) + return [[wrapper() attachmentView] accessibilityIsIgnored]; + + // Attachments are ignored by default (unless we determine that we should expose them). + return true; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + if (isMenuListPopup() || isMenuListOption()) + return IgnoreObject; + + // Never expose an unknown object on the Mac. Clients of the AX API will not know what to do with it. + // Special case is when the unknown object is actually an attachment. + if (roleValue() == UnknownRole && !isAttachment()) + return IgnoreObject; + + return DefaultBehavior; +} + +} // WebCore + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/mac/AccessibilityObjectWrapper.h b/Source/WebCore/accessibility/mac/AccessibilityObjectWrapper.h new file mode 100644 index 0000000..b54d3fc --- /dev/null +++ b/Source/WebCore/accessibility/mac/AccessibilityObjectWrapper.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityObjectWrapper_h +#define AccessibilityObjectWrapper_h + +#ifdef __OBJC__ +@class WebCoreTextMarker; +@class WebCoreTextMarkerRange; +#else +class WebCoreTextMarker; +class WebCoreTextMarkerRange; +#endif + +namespace WebCore { +class AccessibilityObject; +class VisiblePosition; +} + +@interface AccessibilityObjectWrapper : NSObject { + WebCore::AccessibilityObject* m_object; +} + +- (id)initWithAccessibilityObject:(WebCore::AccessibilityObject*)axObject; +- (void)detach; +- (WebCore::AccessibilityObject*)accessibilityObject; + +// Used to inform an element when a notification is posted for it. Used by DRT. +- (void)accessibilityPostedNotification:(NSString *)notificationName; + +- (NSView*)attachmentView; + +@end + +#endif // AccessibilityObjectWrapper_h diff --git a/Source/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm b/Source/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm new file mode 100644 index 0000000..f3b388b --- /dev/null +++ b/Source/WebCore/accessibility/mac/AccessibilityObjectWrapper.mm @@ -0,0 +1,2850 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "AccessibilityObjectWrapper.h" + +#if HAVE(ACCESSIBILITY) + +#import "AXObjectCache.h" +#import "AccessibilityARIAGridRow.h" +#import "AccessibilityListBox.h" +#import "AccessibilityList.h" +#import "AccessibilityRenderObject.h" +#import "AccessibilityScrollView.h" +#import "AccessibilityTable.h" +#import "AccessibilityTableCell.h" +#import "AccessibilityTableRow.h" +#import "AccessibilityTableColumn.h" +#import "ColorMac.h" +#import "EditorClient.h" +#import "Frame.h" +#import "FrameLoaderClient.h" +#import "HTMLAnchorElement.h" +#import "HTMLAreaElement.h" +#import "HTMLFrameOwnerElement.h" +#import "HTMLImageElement.h" +#import "HTMLInputElement.h" +#import "HTMLTextAreaElement.h" +#import "LocalizedStrings.h" +#import "RenderTextControl.h" +#import "RenderView.h" +#import "RenderWidget.h" +#import "ScrollView.h" +#import "SelectionController.h" +#import "SimpleFontData.h" +#import "TextIterator.h" +#import "WebCoreFrameView.h" +#import "WebCoreObjCExtras.h" +#import "WebCoreViewFactory.h" +#import "htmlediting.h" +#import "visible_units.h" + +using namespace WebCore; +using namespace HTMLNames; +using namespace std; + +// Cell Tables +#ifndef NSAccessibilitySelectedCellsAttribute +#define NSAccessibilitySelectedCellsAttribute @"AXSelectedCells" +#endif + +#ifndef NSAccessibilityVisibleCellsAttribute +#define NSAccessibilityVisibleCellsAttribute @"AXVisibleCells" +#endif + +#ifndef NSAccessibilityRowHeaderUIElementsAttribute +#define NSAccessibilityRowHeaderUIElementsAttribute @"AXRowHeaderUIElements" +#endif + +#ifndef NSAccessibilityRowIndexRangeAttribute +#define NSAccessibilityRowIndexRangeAttribute @"AXRowIndexRange" +#endif + +#ifndef NSAccessibilityColumnIndexRangeAttribute +#define NSAccessibilityColumnIndexRangeAttribute @"AXColumnIndexRange" +#endif + +#ifndef NSAccessibilityCellForColumnAndRowParameterizedAttribute +#define NSAccessibilityCellForColumnAndRowParameterizedAttribute @"AXCellForColumnAndRow" +#endif + +#ifndef NSAccessibilityCellRole +#define NSAccessibilityCellRole @"AXCell" +#endif + +// Lists +#ifndef NSAccessibilityContentListSubrole +#define NSAccessibilityContentListSubrole @"AXContentList" +#endif + +#ifndef NSAccessibilityDefinitionListSubrole +#define NSAccessibilityDefinitionListSubrole @"AXDefinitionList" +#endif + +// Miscellaneous +#ifndef NSAccessibilityBlockQuoteLevelAttribute +#define NSAccessibilityBlockQuoteLevelAttribute @"AXBlockQuoteLevel" +#endif + +#ifndef NSAccessibilityAccessKeyAttribute +#define NSAccessibilityAccessKeyAttribute @"AXAccessKey" +#endif + +#ifndef NSAccessibilityLanguageAttribute +#define NSAccessibilityLanguageAttribute @"AXLanguage" +#endif + +#ifndef NSAccessibilityRequiredAttribute +#define NSAccessibilityRequiredAttribute @"AXRequired" +#endif + +#ifndef NSAccessibilityInvalidAttribute +#define NSAccessibilityInvalidAttribute @"AXInvalid" +#endif + +#ifndef NSAccessibilityOwnsAttribute +#define NSAccessibilityOwnsAttribute @"AXOwns" +#endif + +#ifndef NSAccessibilityGrabbedAttribute +#define NSAccessibilityGrabbedAttribute @"AXGrabbed" +#endif + +#ifndef NSAccessibilityDropEffectsAttribute +#define NSAccessibilityDropEffectsAttribute @"AXDropEffects" +#endif + +#ifndef NSAccessibilityARIALiveAttribute +#define NSAccessibilityARIALiveAttribute @"AXARIALive" +#endif + +#ifndef NSAccessibilityARIAAtomicAttribute +#define NSAccessibilityARIAAtomicAttribute @"AXARIAAtomic" +#endif + +#ifndef NSAccessibilityARIARelevantAttribute +#define NSAccessibilityARIARelevantAttribute @"AXARIARelevant" +#endif + +#ifndef NSAccessibilityARIABusyAttribute +#define NSAccessibilityARIABusyAttribute @"AXARIABusy" +#endif + +#ifndef NSAccessibilityLoadingProgressAttribute +#define NSAccessibilityLoadingProgressAttribute @"AXLoadingProgress" +#endif + +#ifndef NSAccessibilityHasPopupAttribute +#define NSAccessibilityHasPopupAttribute @"AXHasPopup" +#endif + +#ifndef NSAccessibilityPlaceholderValueAttribute +#define NSAccessibilityPlaceholderValueAttribute @"AXPlaceholderValue" +#endif + +#ifdef BUILDING_ON_TIGER +typedef unsigned NSUInteger; +#define NSAccessibilityValueDescriptionAttribute @"AXValueDescription" +#define NSAccessibilityTimelineSubrole @"AXTimeline" +#endif + +@interface NSObject (WebKitAccessibilityArrayCategory) + +- (NSUInteger)accessibilityIndexOfChild:(id)child; +- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute; +- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount; + +@end + +@implementation AccessibilityObjectWrapper + +- (id)initWithAccessibilityObject:(AccessibilityObject*)axObject +{ + [super init]; + + m_object = axObject; + return self; +} + +- (void)unregisterUniqueIdForUIElement +{ + [[WebCoreViewFactory sharedFactory] unregisterUniqueIdForUIElement:self]; +} + +- (void)detach +{ + // Send unregisterUniqueIdForUIElement unconditionally because if it is + // ever accidentally not done (via other bugs in our AX implementation) you + // end up with a crash like <rdar://problem/4273149>. It is safe and not + // expensive to send even if the object is not registered. + [self unregisterUniqueIdForUIElement]; + m_object = 0; +} + +- (BOOL)updateObjectBackingStore +{ + // Calling updateBackingStore() can invalidate this element so self must be retained. + // If it does become invalidated, m_object will be nil. + [[self retain] autorelease]; + + if (!m_object) + return NO; + + m_object->updateBackingStore(); + if (!m_object) + return NO; + + return YES; +} + +- (AccessibilityObject*)accessibilityObject +{ + return m_object; +} + +- (NSView*)attachmentView +{ + ASSERT(m_object->isAttachment()); + Widget* widget = m_object->widgetForAttachmentView(); + if (!widget) + return nil; + return NSAccessibilityUnignoredDescendant(widget->platformWidget()); +} + +static WebCoreTextMarker* textMarkerForVisiblePosition(AXObjectCache* cache, const VisiblePosition& visiblePos) +{ + ASSERT(cache); + + TextMarkerData textMarkerData; + cache->textMarkerDataForVisiblePosition(textMarkerData, visiblePos); + if (!textMarkerData.axID) + return nil; + + return [[WebCoreViewFactory sharedFactory] textMarkerWithBytes:&textMarkerData length:sizeof(textMarkerData)]; +} + +- (WebCoreTextMarker *)textMarkerForVisiblePosition:(const VisiblePosition &)visiblePos +{ + return textMarkerForVisiblePosition(m_object->axObjectCache(), visiblePos); +} + +static VisiblePosition visiblePositionForTextMarker(AXObjectCache* cache, WebCoreTextMarker* textMarker) +{ + ASSERT(cache); + + if (!textMarker) + return VisiblePosition(); + TextMarkerData textMarkerData; + if (![[WebCoreViewFactory sharedFactory] getBytes:&textMarkerData fromTextMarker:textMarker length:sizeof(textMarkerData)]) + return VisiblePosition(); + + return cache->visiblePositionForTextMarkerData(textMarkerData); +} + +- (VisiblePosition)visiblePositionForTextMarker:(WebCoreTextMarker *)textMarker +{ + return visiblePositionForTextMarker(m_object->axObjectCache(), textMarker); +} + +static VisiblePosition visiblePositionForStartOfTextMarkerRange(AXObjectCache *cache, WebCoreTextMarkerRange* textMarkerRange) +{ + return visiblePositionForTextMarker(cache, [[WebCoreViewFactory sharedFactory] startOfTextMarkerRange:textMarkerRange]); +} + +static VisiblePosition visiblePositionForEndOfTextMarkerRange(AXObjectCache *cache, WebCoreTextMarkerRange* textMarkerRange) +{ + return visiblePositionForTextMarker(cache, [[WebCoreViewFactory sharedFactory] endOfTextMarkerRange:textMarkerRange]); +} + +static WebCoreTextMarkerRange* textMarkerRangeFromMarkers(WebCoreTextMarker* textMarker1, WebCoreTextMarker* textMarker2) +{ + if (!textMarker1 || !textMarker2) + return nil; + + return [[WebCoreViewFactory sharedFactory] textMarkerRangeWithStart:textMarker1 end:textMarker2]; +} + +static void AXAttributeStringSetFont(NSMutableAttributedString* attrString, NSString* attribute, NSFont* font, NSRange range) +{ + NSDictionary* dict; + + if (font) { + dict = [NSDictionary dictionaryWithObjectsAndKeys: + [font fontName] , NSAccessibilityFontNameKey, + [font familyName] , NSAccessibilityFontFamilyKey, + [font displayName] , NSAccessibilityVisibleNameKey, + [NSNumber numberWithFloat:[font pointSize]] , NSAccessibilityFontSizeKey, + nil]; + + [attrString addAttribute:attribute value:dict range:range]; + } else + [attrString removeAttribute:attribute range:range]; + +} + +static CGColorRef CreateCGColorIfDifferent(NSColor* nsColor, CGColorRef existingColor) +{ + // get color information assuming NSDeviceRGBColorSpace + NSColor* rgbColor = [nsColor colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + if (rgbColor == nil) + rgbColor = [NSColor blackColor]; + CGFloat components[4]; + [rgbColor getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; + + // create a new CGColorRef to return + CGColorSpaceRef cgColorSpace = CGColorSpaceCreateDeviceRGB(); + CGColorRef cgColor = CGColorCreate(cgColorSpace, components); + CGColorSpaceRelease(cgColorSpace); + + // check for match with existing color + if (existingColor && CGColorEqualToColor(cgColor, existingColor)) { + CGColorRelease(cgColor); + cgColor = 0; + } + + return cgColor; +} + +static void AXAttributeStringSetColor(NSMutableAttributedString* attrString, NSString* attribute, NSColor* color, NSRange range) +{ + if (color) { + CGColorRef existingColor = (CGColorRef) [attrString attribute:attribute atIndex:range.location effectiveRange:nil]; + CGColorRef cgColor = CreateCGColorIfDifferent(color, existingColor); + if (cgColor) { + [attrString addAttribute:attribute value:(id)cgColor range:range]; + CGColorRelease(cgColor); + } + } else + [attrString removeAttribute:attribute range:range]; +} + +static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range) +{ + if (number) + [attrString addAttribute:attribute value:number range:range]; + else + [attrString removeAttribute:attribute range:range]; +} + +static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) +{ + RenderStyle* style = renderer->style(); + + // set basic font info + AXAttributeStringSetFont(attrString, NSAccessibilityFontTextAttribute, style->font().primaryFont()->getNSFont(), range); + + // set basic colors + AXAttributeStringSetColor(attrString, NSAccessibilityForegroundColorTextAttribute, nsColor(style->visitedDependentColor(CSSPropertyColor)), range); + AXAttributeStringSetColor(attrString, NSAccessibilityBackgroundColorTextAttribute, nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)), range); + + // set super/sub scripting + EVerticalAlign alignment = style->verticalAlign(); + if (alignment == SUB) + AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:(-1)], range); + else if (alignment == SUPER) + AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:1], range); + else + [attrString removeAttribute:NSAccessibilitySuperscriptTextAttribute range:range]; + + // set shadow + if (style->textShadow()) + AXAttributeStringSetNumber(attrString, NSAccessibilityShadowTextAttribute, [NSNumber numberWithBool:YES], range); + else + [attrString removeAttribute:NSAccessibilityShadowTextAttribute range:range]; + + // set underline and strikethrough + int decor = style->textDecorationsInEffect(); + if ((decor & UNDERLINE) == 0) { + [attrString removeAttribute:NSAccessibilityUnderlineTextAttribute range:range]; + [attrString removeAttribute:NSAccessibilityUnderlineColorTextAttribute range:range]; + } + + if ((decor & LINE_THROUGH) == 0) { + [attrString removeAttribute:NSAccessibilityStrikethroughTextAttribute range:range]; + [attrString removeAttribute:NSAccessibilityStrikethroughColorTextAttribute range:range]; + } + + if ((decor & (UNDERLINE | LINE_THROUGH)) != 0) { + // find colors using quirk mode approach (strict mode would use current + // color for all but the root line box, which would use getTextDecorationColors) + Color underline, overline, linethrough; + renderer->getTextDecorationColors(decor, underline, overline, linethrough); + + if ((decor & UNDERLINE) != 0) { + AXAttributeStringSetNumber(attrString, NSAccessibilityUnderlineTextAttribute, [NSNumber numberWithBool:YES], range); + AXAttributeStringSetColor(attrString, NSAccessibilityUnderlineColorTextAttribute, nsColor(underline), range); + } + + if ((decor & LINE_THROUGH) != 0) { + AXAttributeStringSetNumber(attrString, NSAccessibilityStrikethroughTextAttribute, [NSNumber numberWithBool:YES], range); + AXAttributeStringSetColor(attrString, NSAccessibilityStrikethroughColorTextAttribute, nsColor(linethrough), range); + } + } +} + +static int blockquoteLevel(RenderObject* renderer) +{ + if (!renderer) + return 0; + + int result = 0; + for (Node* node = renderer->node(); node; node = node->parentNode()) { + if (node->hasTagName(blockquoteTag)) + result += 1; + } + + return result; +} + +static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) +{ + int quoteLevel = blockquoteLevel(renderer); + + if (quoteLevel) + [attrString addAttribute:NSAccessibilityBlockQuoteLevelAttribute value:[NSNumber numberWithInt:quoteLevel] range:range]; + else + [attrString removeAttribute:NSAccessibilityBlockQuoteLevelAttribute range:range]; +} + +static void AXAttributeStringSetSpelling(NSMutableAttributedString* attrString, Node* node, const UChar* chars, int charLength, NSRange range) +{ + // Check the spelling directly since document->markersForNode() does not store the misspelled marking when the cursor is in a word. + EditorClient* client = node->document()->page()->editorClient(); + int currentPosition = 0; + while (charLength > 0) { + const UChar* charData = chars + currentPosition; + + int misspellingLocation = -1; + int misspellingLength = 0; + client->checkSpellingOfString(charData, charLength, &misspellingLocation, &misspellingLength); + if (misspellingLocation == -1 || !misspellingLength) + break; + + NSRange spellRange = NSMakeRange(range.location + currentPosition + misspellingLocation, misspellingLength); + AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, [NSNumber numberWithBool:YES], spellRange); + charLength -= (misspellingLocation + misspellingLength); + currentPosition += (misspellingLocation + misspellingLength); + } +} + +static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) +{ + if (!renderer) + return; + + AccessibilityObject* parentObject = renderer->document()->axObjectCache()->getOrCreate(renderer->parent()); + int parentHeadingLevel = parentObject->headingLevel(); + + if (parentHeadingLevel) + [attrString addAttribute:@"AXHeadingLevel" value:[NSNumber numberWithInt:parentHeadingLevel] range:range]; + else + [attrString removeAttribute:@"AXHeadingLevel" range:range]; +} + +static void AXAttributeStringSetElement(NSMutableAttributedString* attrString, NSString* attribute, AccessibilityObject* object, NSRange range) +{ + if (object && object->isAccessibilityRenderObject()) { + // make a serializable AX object + + RenderObject* renderer = static_cast<AccessibilityRenderObject*>(object)->renderer(); + if (!renderer) + return; + + Document* doc = renderer->document(); + if (!doc) + return; + + AXObjectCache* cache = doc->axObjectCache(); + if (!cache) + return; + + AXUIElementRef axElement = [[WebCoreViewFactory sharedFactory] AXUIElementForElement:object->wrapper()]; + if (axElement) { + [attrString addAttribute:attribute value:(id)axElement range:range]; + CFRelease(axElement); + } + } else + [attrString removeAttribute:attribute range:range]; +} + +static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, const UChar* chars, int length) +{ + // skip invisible text + if (!node->renderer()) + return; + + // easier to calculate the range before appending the string + NSRange attrStringRange = NSMakeRange([attrString length], length); + + // append the string from this node + [[attrString mutableString] appendString:[NSString stringWithCharacters:chars length:length]]; + + // add new attributes and remove irrelevant inherited ones + // NOTE: color attributes are handled specially because -[NSMutableAttributedString addAttribute: value: range:] does not merge + // identical colors. Workaround is to not replace an existing color attribute if it matches what we are adding. This also means + // we cannot just pre-remove all inherited attributes on the appended string, so we have to remove the irrelevant ones individually. + + // remove inherited attachment from prior AXAttributedStringAppendReplaced + [attrString removeAttribute:NSAccessibilityAttachmentTextAttribute range:attrStringRange]; + [attrString removeAttribute:NSAccessibilityMisspelledTextAttribute range:attrStringRange]; + + // set new attributes + AXAttributeStringSetStyle(attrString, node->renderer(), attrStringRange); + AXAttributeStringSetHeadingLevel(attrString, node->renderer(), attrStringRange); + AXAttributeStringSetBlockquoteLevel(attrString, node->renderer(), attrStringRange); + AXAttributeStringSetElement(attrString, NSAccessibilityLinkTextAttribute, AccessibilityObject::anchorElementForNode(node), attrStringRange); + + // do spelling last because it tends to break up the range + AXAttributeStringSetSpelling(attrString, node, chars, length, attrStringRange); +} + +static NSString* nsStringForReplacedNode(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()) { + ASSERT_NOT_REACHED(); + return nil; + } + + // create an AX object, but skip it if it is not supposed to be seen + RefPtr<AccessibilityObject> obj = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer()); + if (obj->accessibilityIsIgnored()) + return nil; + + // use the attachmentCharacter to represent the replaced node + const UniChar attachmentChar = NSAttachmentCharacter; + return [NSString stringWithCharacters:&attachmentChar length:1]; +} + +- (NSAttributedString*)doAXAttributedStringForTextMarkerRange:(WebCoreTextMarkerRange*)textMarkerRange +{ + if (!m_object) + return nil; + + // extract the start and end VisiblePosition + VisiblePosition startVisiblePosition = visiblePositionForStartOfTextMarkerRange(m_object->axObjectCache(), textMarkerRange); + if (startVisiblePosition.isNull()) + return nil; + + VisiblePosition endVisiblePosition = visiblePositionForEndOfTextMarkerRange(m_object->axObjectCache(), textMarkerRange); + if (endVisiblePosition.isNull()) + return nil; + + VisiblePositionRange visiblePositionRange(startVisiblePosition, endVisiblePosition); + // iterate over the range to build the AX attributed string + NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init]; + TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get()); + while (!it.atEnd()) { + // locate the node and starting offset for this range + int exception = 0; + Node* node = it.range()->startContainer(exception); + ASSERT(node == it.range()->endContainer(exception)); + int offset = it.range()->startOffset(exception); + + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.length() != 0) { + // Add the text of the list marker item if necessary. + String listMarkerText = m_object->listMarkerTextForNodeAndPosition(node, VisiblePosition(it.range()->startPosition())); + if (!listMarkerText.isEmpty()) + AXAttributedStringAppendText(attrString, node, listMarkerText.characters(), listMarkerText.length()); + + AXAttributedStringAppendText(attrString, node, it.characters(), it.length()); + } else { + Node* replacedNode = node->childNode(offset); + NSString *attachmentString = nsStringForReplacedNode(replacedNode); + if (attachmentString) { + NSRange attrStringRange = NSMakeRange([attrString length], [attachmentString length]); + + // append the placeholder string + [[attrString mutableString] appendString:attachmentString]; + + // remove all inherited attributes + [attrString setAttributes:nil range:attrStringRange]; + + // add the attachment attribute + AccessibilityObject* obj = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer()); + AXAttributeStringSetElement(attrString, NSAccessibilityAttachmentTextAttribute, obj, attrStringRange); + } + } + it.advance(); + } + + return [attrString autorelease]; +} + +static WebCoreTextMarkerRange* textMarkerRangeFromVisiblePositions(AXObjectCache *cache, VisiblePosition startPosition, VisiblePosition endPosition) +{ + WebCoreTextMarker* startTextMarker = textMarkerForVisiblePosition(cache, startPosition); + WebCoreTextMarker* endTextMarker = textMarkerForVisiblePosition(cache, endPosition); + return textMarkerRangeFromMarkers(startTextMarker, endTextMarker); +} + +- (WebCoreTextMarkerRange *)textMarkerRangeFromVisiblePositions:(VisiblePosition)startPosition endPosition:(VisiblePosition)endPosition +{ + return textMarkerRangeFromVisiblePositions(m_object->axObjectCache(), startPosition, endPosition); +} + +- (NSArray*)accessibilityActionNames +{ + if (![self updateObjectBackingStore]) + return nil; + + static NSArray* actionElementActions = [[NSArray alloc] initWithObjects: NSAccessibilityPressAction, NSAccessibilityShowMenuAction, nil]; + static NSArray* defaultElementActions = [[NSArray alloc] initWithObjects: NSAccessibilityShowMenuAction, nil]; + static NSArray* menuElementActions = [[NSArray alloc] initWithObjects: NSAccessibilityCancelAction, NSAccessibilityPressAction, nil]; + static NSArray* sliderActions = [[NSArray alloc] initWithObjects: NSAccessibilityIncrementAction, NSAccessibilityDecrementAction, nil]; + + NSArray *actions; + if (m_object->actionElement()) + actions = actionElementActions; + else if (m_object->isMenuRelated()) + actions = menuElementActions; + else if (m_object->isSlider()) + actions = sliderActions; + else if (m_object->isAttachment()) + actions = [[self attachmentView] accessibilityActionNames]; + else + actions = defaultElementActions; + + return actions; +} + +- (NSArray*)additionalAccessibilityAttributeNames +{ + if (!m_object) + return nil; + + NSMutableArray *additional = [NSMutableArray array]; + if (m_object->supportsARIAOwns()) + [additional addObject:NSAccessibilityOwnsAttribute]; + + if (m_object->supportsARIAExpanded()) + [additional addObject:NSAccessibilityExpandedAttribute]; + + if (m_object->isScrollbar()) + [additional addObject:NSAccessibilityOrientationAttribute]; + + if (m_object->supportsARIADragging()) + [additional addObject:NSAccessibilityGrabbedAttribute]; + + if (m_object->supportsARIADropping()) + [additional addObject:NSAccessibilityDropEffectsAttribute]; + + if (m_object->isAccessibilityTable() && static_cast<AccessibilityTable*>(m_object)->supportsSelectedRows()) + [additional addObject:NSAccessibilitySelectedRowsAttribute]; + + if (m_object->supportsARIALiveRegion()) { + [additional addObject:NSAccessibilityARIALiveAttribute]; + [additional addObject:NSAccessibilityARIARelevantAttribute]; + } + + // If an object is a child of a live region, then add these + if (m_object->isInsideARIALiveRegion()) { + [additional addObject:NSAccessibilityARIAAtomicAttribute]; + [additional addObject:NSAccessibilityARIABusyAttribute]; + } + + if (m_object->ariaHasPopup()) + [additional addObject:NSAccessibilityHasPopupAttribute]; + + return additional; +} + +- (NSArray*)accessibilityAttributeNames +{ + if (![self updateObjectBackingStore]) + return nil; + + if (m_object->isAttachment()) + return [[self attachmentView] accessibilityAttributeNames]; + + static NSArray* attributes = nil; + static NSArray* anchorAttrs = nil; + static NSArray* webAreaAttrs = nil; + static NSArray* textAttrs = nil; + static NSArray* listAttrs = nil; + static NSArray* listBoxAttrs = nil; + static NSArray* rangeAttrs = nil; + static NSArray* commonMenuAttrs = nil; + static NSArray* menuAttrs = nil; + static NSArray* menuBarAttrs = nil; + static NSArray* menuItemAttrs = nil; + static NSArray* menuButtonAttrs = nil; + static NSArray* controlAttrs = nil; + static NSArray* tableAttrs = nil; + static NSArray* tableRowAttrs = nil; + static NSArray* tableColAttrs = nil; + static NSArray* tableCellAttrs = nil; + 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; + static NSArray* scrollViewAttrs = nil; + NSMutableArray* tempArray; + if (attributes == nil) { + attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, + NSAccessibilitySubroleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityChildrenAttribute, + NSAccessibilityHelpAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilitySizeAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityWindowAttribute, + @"AXSelectedTextMarkerRange", + @"AXStartTextMarker", + @"AXEndTextMarker", + @"AXVisited", + NSAccessibilityLinkedUIElementsAttribute, + NSAccessibilitySelectedAttribute, + NSAccessibilityBlockQuoteLevelAttribute, + NSAccessibilityTopLevelUIElementAttribute, + nil]; + } + if (commonMenuAttrs == nil) { + commonMenuAttrs = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityChildrenAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilitySizeAttribute, + nil]; + } + if (anchorAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityURLAttribute]; + [tempArray addObject:NSAccessibilityAccessKeyAttribute]; + anchorAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (webAreaAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:@"AXLinkUIElements"]; + [tempArray addObject:@"AXLoaded"]; + [tempArray addObject:@"AXLayoutCount"]; + [tempArray addObject:NSAccessibilityLoadingProgressAttribute]; + [tempArray addObject:NSAccessibilityURLAttribute]; + webAreaAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (textAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityNumberOfCharactersAttribute]; + [tempArray addObject:NSAccessibilitySelectedTextAttribute]; + [tempArray addObject:NSAccessibilitySelectedTextRangeAttribute]; + [tempArray addObject:NSAccessibilityVisibleCharacterRangeAttribute]; + [tempArray addObject:NSAccessibilityInsertionPointLineNumberAttribute]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + [tempArray addObject:NSAccessibilityAccessKeyAttribute]; + [tempArray addObject:NSAccessibilityRequiredAttribute]; + [tempArray addObject:NSAccessibilityInvalidAttribute]; + [tempArray addObject:NSAccessibilityPlaceholderValueAttribute]; + textAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (listAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilitySelectedChildrenAttribute]; + [tempArray addObject:NSAccessibilityVisibleChildrenAttribute]; + [tempArray addObject:NSAccessibilityOrientationAttribute]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + listAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (listBoxAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:listAttrs]; + [tempArray addObject:NSAccessibilityAccessKeyAttribute]; + [tempArray addObject:NSAccessibilityRequiredAttribute]; + [tempArray addObject:NSAccessibilityInvalidAttribute]; + listBoxAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (rangeAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityMinValueAttribute]; + [tempArray addObject:NSAccessibilityMaxValueAttribute]; + [tempArray addObject:NSAccessibilityOrientationAttribute]; + [tempArray addObject:NSAccessibilityValueDescriptionAttribute]; + rangeAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (menuBarAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs]; + [tempArray addObject:NSAccessibilitySelectedChildrenAttribute]; + [tempArray addObject:NSAccessibilityVisibleChildrenAttribute]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + menuBarAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (menuAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs]; + [tempArray addObject:NSAccessibilitySelectedChildrenAttribute]; + [tempArray addObject:NSAccessibilityVisibleChildrenAttribute]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + menuAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (menuItemAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs]; + [tempArray addObject:NSAccessibilityTitleAttribute]; + [tempArray addObject:NSAccessibilityHelpAttribute]; + [tempArray addObject:NSAccessibilitySelectedAttribute]; + [tempArray addObject:(NSString*)kAXMenuItemCmdCharAttribute]; + [tempArray addObject:(NSString*)kAXMenuItemCmdVirtualKeyAttribute]; + [tempArray addObject:(NSString*)kAXMenuItemCmdGlyphAttribute]; + [tempArray addObject:(NSString*)kAXMenuItemCmdModifiersAttribute]; + [tempArray addObject:(NSString*)kAXMenuItemMarkCharAttribute]; + [tempArray addObject:(NSString*)kAXMenuItemPrimaryUIElementAttribute]; + [tempArray addObject:NSAccessibilityServesAsTitleForUIElementsAttribute]; + menuItemAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (menuButtonAttrs == nil) { + menuButtonAttrs = [[NSArray alloc] initWithObjects:NSAccessibilityRoleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilitySizeAttribute, + NSAccessibilityWindowAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityChildrenAttribute, nil]; + } + if (controlAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + [tempArray addObject:NSAccessibilityAccessKeyAttribute]; + [tempArray addObject:NSAccessibilityRequiredAttribute]; + [tempArray addObject:NSAccessibilityInvalidAttribute]; + 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]; + [tempArray addObject:NSAccessibilityVisibleRowsAttribute]; + [tempArray addObject:NSAccessibilityColumnsAttribute]; + [tempArray addObject:NSAccessibilityVisibleColumnsAttribute]; + [tempArray addObject:NSAccessibilityVisibleCellsAttribute]; + [tempArray addObject:(NSString *)kAXColumnHeaderUIElementsAttribute]; + [tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute]; + [tempArray addObject:NSAccessibilityHeaderAttribute]; + tableAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (tableRowAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityIndexAttribute]; + tableRowAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (tableColAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityIndexAttribute]; + [tempArray addObject:NSAccessibilityHeaderAttribute]; + [tempArray addObject:NSAccessibilityRowsAttribute]; + [tempArray addObject:NSAccessibilityVisibleRowsAttribute]; + tableColAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (tableCellAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityRowIndexRangeAttribute]; + [tempArray addObject:NSAccessibilityColumnIndexRangeAttribute]; + tableCellAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (groupAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + groupAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (inputImageAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:buttonAttrs]; + [tempArray addObject:NSAccessibilityURLAttribute]; + inputImageAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (passwordFieldAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; + [tempArray addObject:NSAccessibilityRequiredAttribute]; + [tempArray addObject:NSAccessibilityInvalidAttribute]; + 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:NSAccessibilityDisclosingAttribute]; + [tempArray addObject:NSAccessibilityDisclosedByRowAttribute]; + [tempArray addObject:NSAccessibilityDisclosureLevelAttribute]; + [tempArray addObject:NSAccessibilityDisclosedRowsAttribute]; + outlineRowAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (scrollViewAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject:NSAccessibilityContentsAttribute]; + [tempArray addObject:NSAccessibilityHorizontalScrollBarAttribute]; + [tempArray addObject:NSAccessibilityVerticalScrollBarAttribute]; + scrollViewAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + NSArray *objectAttributes = 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->isAccessibilityTable()) + objectAttributes = tableAttrs; + else if (m_object->isTableColumn()) + objectAttributes = tableColAttrs; + else if (m_object->isTableCell()) + objectAttributes = tableCellAttrs; + else if (m_object->isTableRow()) { + // An ARIA table row can be collapsed and expanded, so it needs the extra attributes. + if (m_object->isARIATreeGridRow()) + objectAttributes = outlineRowAttrs; + else + objectAttributes = tableRowAttrs; + } + + else if (m_object->isTree()) + objectAttributes = outlineAttrs; + else if (m_object->isTreeItem()) + objectAttributes = outlineRowAttrs; + + else if (m_object->isListBox()) + objectAttributes = listBoxAttrs; + else if (m_object->isList()) + objectAttributes = listAttrs; + + 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() || m_object->isListItem()) + objectAttributes = groupAttrs; + else if (m_object->isTabList()) + objectAttributes = tabListAttrs; + else if (m_object->isScrollView()) + objectAttributes = scrollViewAttrs; + + 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 +{ + if (!textMarkerRange) + return VisiblePositionRange(); + AXObjectCache* cache = m_object->axObjectCache(); + return VisiblePositionRange(visiblePositionForStartOfTextMarkerRange(cache, textMarkerRange), visiblePositionForEndOfTextMarkerRange(cache, textMarkerRange)); +} + +- (NSArray*)renderWidgetChildren +{ + Widget* widget = m_object->widget(); + if (!widget) + return nil; + return [(widget->platformWidget()) accessibilityAttributeValue: NSAccessibilityChildrenAttribute]; +} + +- (id)remoteAccessibilityParentObject +{ + if (!m_object || !m_object->document()) + return nil; + + return m_object->document()->frame()->loader()->client()->accessibilityRemoteObject(); +} + +static void convertToVector(NSArray* array, AccessibilityObject::AccessibilityChildrenVector& vector) +{ + unsigned length = [array count]; + vector.reserveInitialCapacity(length); + for (unsigned i = 0; i < length; ++i) { + AccessibilityObject* obj = [[array objectAtIndex:i] accessibilityObject]; + if (obj) + vector.append(obj); + } +} + +static NSMutableArray* convertToNSArray(const AccessibilityObject::AccessibilityChildrenVector& vector) +{ + unsigned length = vector.size(); + NSMutableArray* array = [NSMutableArray arrayWithCapacity: length]; + for (unsigned i = 0; i < length; ++i) { + AccessibilityObjectWrapper* wrapper = vector[i]->wrapper(); + ASSERT(wrapper); + if (wrapper) { + // we want to return the attachment view instead of the object representing the attachment. + // otherwise, we get palindrome errors in the AX hierarchy + if (vector[i]->isAttachment() && [wrapper attachmentView]) + [array addObject:[wrapper attachmentView]]; + else + [array addObject:wrapper]; + } + } + return array; +} + +- (WebCoreTextMarkerRange*)textMarkerRangeForSelection +{ + VisibleSelection selection = m_object->selection(); + if (selection.isNone()) + return nil; + return [self textMarkerRangeFromVisiblePositions:selection.visibleStart() endPosition:selection.visibleEnd()]; +} + +- (NSValue*)position +{ + IntRect rect = m_object->elementRect(); + NSPoint point; + + FrameView* frameView = m_object->documentFrameView(); + id remoteParent = [self remoteAccessibilityParentObject]; + if (remoteParent) { + point = NSMakePoint(rect.x(), rect.y()); + + NSPoint remotePosition = [[remoteParent accessibilityAttributeValue:NSAccessibilityPositionAttribute] pointValue]; + NSSize remoteSize = [[remoteParent accessibilityAttributeValue:NSAccessibilitySizeAttribute] sizeValue]; + + // Get the y position of the WKView (we have to screen-flip and go from bottom left to top left). + CGFloat screenHeight = [[[NSScreen screens] objectAtIndex:0] frame].size.height; + remotePosition.y = (screenHeight - remotePosition.y) - remoteSize.height; + + NSPoint scrollPosition = NSMakePoint(0, 0); + if (frameView && !m_object->isScrollbar() && !m_object->isScrollView()) { + IntPoint frameScrollPos = frameView->scrollPosition(); + scrollPosition = NSMakePoint(frameScrollPos.x(), frameScrollPos.y()); + } + + point.x += remotePosition.x - scrollPosition.x; + // Set the new position, which means getting bottom y, and then flipping to screen coordinates. + point.y = screenHeight - (point.y + remotePosition.y + rect.height() - scrollPosition.y); + } else { + // The Cocoa accessibility API wants the lower-left corner. + point = NSMakePoint(rect.x(), rect.bottom()); + + if (frameView) { + NSView* view = frameView->documentView(); + point = [[view window] convertBaseToScreen:[view convertPoint: point toView:nil]]; + } + } + + return [NSValue valueWithPoint:point]; +} + +typedef HashMap<int, NSString*> AccessibilityRoleMap; + +static const AccessibilityRoleMap& createAccessibilityRoleMap() +{ + struct RoleEntry { + AccessibilityRole value; + NSString* string; + }; + + static const RoleEntry roles[] = { + { UnknownRole, NSAccessibilityUnknownRole }, + { ButtonRole, NSAccessibilityButtonRole }, + { RadioButtonRole, NSAccessibilityRadioButtonRole }, + { CheckBoxRole, NSAccessibilityCheckBoxRole }, + { SliderRole, NSAccessibilitySliderRole }, + { TabGroupRole, NSAccessibilityTabGroupRole }, + { TextFieldRole, NSAccessibilityTextFieldRole }, + { StaticTextRole, NSAccessibilityStaticTextRole }, + { TextAreaRole, NSAccessibilityTextAreaRole }, + { ScrollAreaRole, NSAccessibilityScrollAreaRole }, + { PopUpButtonRole, NSAccessibilityPopUpButtonRole }, + { MenuButtonRole, NSAccessibilityMenuButtonRole }, + { TableRole, NSAccessibilityTableRole }, + { ApplicationRole, NSAccessibilityApplicationRole }, + { GroupRole, NSAccessibilityGroupRole }, + { RadioGroupRole, NSAccessibilityRadioGroupRole }, + { ListRole, NSAccessibilityListRole }, + { DirectoryRole, NSAccessibilityListRole }, + { ScrollBarRole, NSAccessibilityScrollBarRole }, + { ValueIndicatorRole, NSAccessibilityValueIndicatorRole }, + { ImageRole, NSAccessibilityImageRole }, + { MenuBarRole, NSAccessibilityMenuBarRole }, + { MenuRole, NSAccessibilityMenuRole }, + { MenuItemRole, NSAccessibilityMenuItemRole }, + { ColumnRole, NSAccessibilityColumnRole }, + { RowRole, NSAccessibilityRowRole }, + { ToolbarRole, NSAccessibilityToolbarRole }, + { BusyIndicatorRole, NSAccessibilityBusyIndicatorRole }, + { ProgressIndicatorRole, NSAccessibilityProgressIndicatorRole }, + { WindowRole, NSAccessibilityWindowRole }, + { DrawerRole, NSAccessibilityDrawerRole }, + { SystemWideRole, NSAccessibilitySystemWideRole }, + { OutlineRole, NSAccessibilityOutlineRole }, + { IncrementorRole, NSAccessibilityIncrementorRole }, + { BrowserRole, NSAccessibilityBrowserRole }, + { ComboBoxRole, NSAccessibilityComboBoxRole }, + { SplitGroupRole, NSAccessibilitySplitGroupRole }, + { SplitterRole, NSAccessibilitySplitterRole }, + { ColorWellRole, NSAccessibilityColorWellRole }, + { GrowAreaRole, NSAccessibilityGrowAreaRole }, + { SheetRole, NSAccessibilitySheetRole }, + { HelpTagRole, NSAccessibilityHelpTagRole }, + { MatteRole, NSAccessibilityMatteRole }, + { RulerRole, NSAccessibilityRulerRole }, + { RulerMarkerRole, NSAccessibilityRulerMarkerRole }, + { LinkRole, NSAccessibilityLinkRole }, +#ifndef BUILDING_ON_TIGER + { DisclosureTriangleRole, NSAccessibilityDisclosureTriangleRole }, + { GridRole, NSAccessibilityGridRole }, +#endif + { WebCoreLinkRole, NSAccessibilityLinkRole }, + { ImageMapLinkRole, NSAccessibilityLinkRole }, + { ImageMapRole, @"AXImageMap" }, + { ListMarkerRole, @"AXListMarker" }, + { WebAreaRole, @"AXWebArea" }, + { HeadingRole, @"AXHeading" }, + { ListBoxRole, NSAccessibilityListRole }, + { ListBoxOptionRole, NSAccessibilityStaticTextRole }, +#if ACCESSIBILITY_TABLES + { CellRole, NSAccessibilityCellRole }, +#else + { CellRole, NSAccessibilityGroupRole }, +#endif + { TableHeaderContainerRole, NSAccessibilityGroupRole }, + { DefinitionListDefinitionRole, NSAccessibilityGroupRole }, + { DefinitionListTermRole, NSAccessibilityGroupRole }, + { SliderThumbRole, NSAccessibilityValueIndicatorRole }, + { LandmarkApplicationRole, NSAccessibilityGroupRole }, + { LandmarkBannerRole, NSAccessibilityGroupRole }, + { LandmarkComplementaryRole, NSAccessibilityGroupRole }, + { LandmarkContentInfoRole, NSAccessibilityGroupRole }, + { 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 }, + { ListItemRole, NSAccessibilityGroupRole } + }; + AccessibilityRoleMap& roleMap = *new AccessibilityRoleMap; + + const unsigned numRoles = sizeof(roles) / sizeof(roles[0]); + for (unsigned i = 0; i < numRoles; ++i) + roleMap.set(roles[i].value, roles[i].string); + return roleMap; +} + +static NSString* roleValueToNSString(AccessibilityRole value) +{ + ASSERT(value); + static const AccessibilityRoleMap& roleMap = createAccessibilityRoleMap(); + return roleMap.get(value); +} + +- (NSString*)role +{ + if (m_object->isAttachment()) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleAttribute]; + NSString* string = roleValueToNSString(m_object->roleValue()); + if (string != nil) + return string; + return NSAccessibilityUnknownRole; +} + +- (NSString*)subrole +{ + if (m_object->isPasswordField()) + return NSAccessibilitySecureTextFieldSubrole; + + if (m_object->isAttachment()) { + NSView* attachView = [self attachmentView]; + if ([[attachView accessibilityAttributeNames] containsObject:NSAccessibilitySubroleAttribute]) { + return [attachView accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; + } + } + + if (m_object->isTreeItem()) + return NSAccessibilityOutlineRowSubrole; + + if (m_object->isList()) { + AccessibilityList* listObject = static_cast<AccessibilityList*>(m_object); + if (listObject->isUnorderedList() || listObject->isOrderedList()) + return NSAccessibilityContentListSubrole; + if (listObject->isDefinitionList()) + return NSAccessibilityDefinitionListSubrole; + } + + // ARIA content subroles. + switch (m_object->roleValue()) { + case LandmarkApplicationRole: + return @"AXLandmarkApplication"; + case LandmarkBannerRole: + return @"AXLandmarkBanner"; + case LandmarkComplementaryRole: + return @"AXLandmarkComplementary"; + case LandmarkContentInfoRole: + return @"AXLandmarkContentInfo"; + case LandmarkMainRole: + return @"AXLandmarkMain"; + case LandmarkNavigationRole: + return @"AXLandmarkNavigation"; + case LandmarkSearchRole: + return @"AXLandmarkSearch"; + case ApplicationAlertRole: + return @"AXApplicationAlert"; + case ApplicationAlertDialogRole: + return @"AXApplicationAlertDialog"; + case ApplicationDialogRole: + return @"AXApplicationDialog"; + case ApplicationLogRole: + return @"AXApplicationLog"; + case ApplicationMarqueeRole: + return @"AXApplicationMarquee"; + case ApplicationStatusRole: + return @"AXApplicationStatus"; + case ApplicationTimerRole: + return @"AXApplicationTimer"; + case DocumentRole: + 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: + break; + } + + if (m_object->isMediaTimeline()) + return NSAccessibilityTimelineSubrole; + + return nil; +} + +- (NSString*)roleDescription +{ + if (!m_object) + return nil; + + // attachments have the AXImage role, but a different subrole + if (m_object->isAttachment()) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute]; + + NSString* axRole = [self role]; + + if ([axRole isEqualToString:NSAccessibilityGroupRole]) { + switch (m_object->roleValue()) { + default: + return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, [self subrole]); + case LandmarkApplicationRole: + return AXARIAContentGroupText(@"ARIALandmarkApplication"); + case LandmarkBannerRole: + return AXARIAContentGroupText(@"ARIALandmarkBanner"); + case LandmarkComplementaryRole: + return AXARIAContentGroupText(@"ARIALandmarkComplementary"); + case LandmarkContentInfoRole: + return AXARIAContentGroupText(@"ARIALandmarkContentInfo"); + case LandmarkMainRole: + return AXARIAContentGroupText(@"ARIALandmarkMain"); + case LandmarkNavigationRole: + 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: + return AXARIAContentGroupText(@"ARIAApplicationMarquee"); + case ApplicationStatusRole: + return AXARIAContentGroupText(@"ARIAApplicationStatus"); + case ApplicationTimerRole: + return AXARIAContentGroupText(@"ARIAApplicationTimer"); + case DocumentRole: + 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(); + } + } + + if ([axRole isEqualToString:@"AXWebArea"]) + return AXWebAreaText(); + + if ([axRole isEqualToString:@"AXLink"]) + return AXLinkText(); + + if ([axRole isEqualToString:@"AXListMarker"]) + return AXListMarkerText(); + + if ([axRole isEqualToString:@"AXImageMap"]) + return AXImageMapText(); + + 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]); + if (![defaultRoleDescription isEqualToString:axRole]) + return defaultRoleDescription; + + return NSAccessibilityRoleDescription(NSAccessibilityUnknownRole, nil); +} + +- (id)scrollViewParent +{ + if (!m_object || !m_object->isAccessibilityScrollView()) + return nil; + + // If this scroll view provides it's parent object (because it's a sub-frame), then + // we should not find the remoteAccessibilityParent. + if (m_object->parentObject()) + return nil; + + AccessibilityScrollView* scrollView = toAccessibilityScrollView(m_object); + ScrollView* scroll = scrollView->scrollView(); + if (!scroll) + return nil; + + if (scroll->platformWidget()) + return scroll->platformWidget(); + + return [self remoteAccessibilityParentObject]; +} + +// FIXME: split up this function in a better way. +// suggestions: Use a hash table that maps attribute names to function calls, +// or maybe pointers to member functions +- (id)accessibilityAttributeValue:(NSString*)attributeName +{ + if (![self updateObjectBackingStore]) + return nil; + + if ([attributeName isEqualToString: NSAccessibilityRoleAttribute]) + return [self role]; + + if ([attributeName isEqualToString: NSAccessibilitySubroleAttribute]) + return [self subrole]; + + if ([attributeName isEqualToString: NSAccessibilityRoleDescriptionAttribute]) + return [self roleDescription]; + + if ([attributeName isEqualToString: NSAccessibilityParentAttribute]) { + + // This will return the parent of the AXWebArea, if this is a web area. + id scrollViewParent = [self scrollViewParent]; + if (scrollViewParent) + return scrollViewParent; + + // 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(); + } + } + + AccessibilityObject* parent = m_object->parentObjectUnignored(); + if (parent) + return parent->wrapper(); + return nil; + } + + if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { + if (m_object->children().isEmpty()) { + NSArray* children = [self renderWidgetChildren]; + 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()); + } + + if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) { + if (m_object->isListBox()) { + AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy; + m_object->selectedChildren(selectedChildrenCopy); + return convertToNSArray(selectedChildrenCopy); + } + return nil; + } + + if ([attributeName isEqualToString: NSAccessibilityVisibleChildrenAttribute]) { + if (m_object->isListBox()) { + AccessibilityObject::AccessibilityChildrenVector visibleChildrenCopy; + m_object->visibleChildren(visibleChildrenCopy); + return convertToNSArray(visibleChildrenCopy); + } + else if (m_object->isList()) + return [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute]; + + return nil; + } + + + if (m_object->isWebArea()) { + if ([attributeName isEqualToString:@"AXLinkUIElements"]) { + AccessibilityObject::AccessibilityChildrenVector links; + static_cast<AccessibilityRenderObject*>(m_object)->getDocumentLinks(links); + return convertToNSArray(links); + } + if ([attributeName isEqualToString:@"AXLoaded"]) + return [NSNumber numberWithBool:m_object->isLoaded()]; + if ([attributeName isEqualToString:@"AXLayoutCount"]) + return [NSNumber numberWithInt:m_object->layoutCount()]; + if ([attributeName isEqualToString:NSAccessibilityLoadingProgressAttribute]) + return [NSNumber numberWithDouble:m_object->estimatedLoadingProgress()]; + } + + if (m_object->isTextControl()) { + if ([attributeName isEqualToString: NSAccessibilityNumberOfCharactersAttribute]) { + int length = m_object->textLength(); + if (length < 0) + return nil; + return [NSNumber numberWithUnsignedInt:length]; + } + if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) { + String selectedText = m_object->selectedText(); + if (selectedText.isNull()) + return nil; + return (NSString*)selectedText; + } + if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) { + PlainTextRange textRange = m_object->selectedTextRange(); + if (textRange.isNull()) + return [NSValue valueWithRange:NSMakeRange(0, 0)]; + return [NSValue valueWithRange:NSMakeRange(textRange.start, textRange.length)]; + } + // TODO: Get actual visible range. <rdar://problem/4712101> + if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) + return m_object->isPasswordField() ? nil : [NSValue valueWithRange: NSMakeRange(0, m_object->textLength())]; + if ([attributeName isEqualToString: NSAccessibilityInsertionPointLineNumberAttribute]) { + // if selectionEnd > 0, then there is selected text and this question should not be answered + if (m_object->isPasswordField() || m_object->selectionEnd() > 0) + return nil; + int lineNumber = m_object->lineForPosition(m_object->visiblePositionForIndex(m_object->selectionStart(), true)); + if (lineNumber < 0) + return nil; + return [NSNumber numberWithInt:lineNumber]; + } + } + + if ([attributeName isEqualToString: NSAccessibilityURLAttribute]) { + KURL url = m_object->url(); + if (url.isNull()) + return nil; + return (NSURL*)url; + } + + if ([attributeName isEqualToString: @"AXVisited"]) + return [NSNumber numberWithBool: m_object->isVisited()]; + + if ([attributeName isEqualToString: NSAccessibilityTitleAttribute]) { + if (m_object->isAttachment()) { + if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityTitleAttribute]) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityTitleAttribute]; + } + return m_object->title(); + } + + if ([attributeName isEqualToString: NSAccessibilityDescriptionAttribute]) { + if (m_object->isAttachment()) { + if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityDescriptionAttribute]) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]; + } + return m_object->accessibilityDescription(); + } + + if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) { + if (m_object->isAttachment()) { + if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityValueAttribute]) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityValueAttribute]; + } + if (m_object->isProgressIndicator() || m_object->isSlider() || m_object->isScrollbar()) + return [NSNumber numberWithFloat:m_object->valueForRange()]; + if (m_object->roleValue() == SliderThumbRole) + return [NSNumber numberWithFloat:m_object->parentObject()->valueForRange()]; + if (m_object->isHeading()) + return [NSNumber numberWithInt:m_object->headingLevel()]; + + if (m_object->isCheckboxOrRadio()) { + switch (m_object->checkboxOrRadioValue()) { + case ButtonStateOff: + return [NSNumber numberWithInt:0]; + case ButtonStateOn: + return [NSNumber numberWithInt:1]; + case ButtonStateMixed: + return [NSNumber numberWithInt:2]; + } + } + + // radio groups return the selected radio button as the AXValue + if (m_object->isRadioGroup()) { + AccessibilityObject* radioButton = m_object->selectedRadioButton(); + if (!radioButton) + return nil; + 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(); + } + + if ([attributeName isEqualToString: NSAccessibilityMinValueAttribute]) + return [NSNumber numberWithFloat:m_object->minValueForRange()]; + + if ([attributeName isEqualToString: NSAccessibilityMaxValueAttribute]) + return [NSNumber numberWithFloat:m_object->maxValueForRange()]; + + if ([attributeName isEqualToString: NSAccessibilityHelpAttribute]) + return m_object->helpText(); + + if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) + return [NSNumber numberWithBool: m_object->isFocused()]; + + if ([attributeName isEqualToString: NSAccessibilityEnabledAttribute]) + return [NSNumber numberWithBool: m_object->isEnabled()]; + + if ([attributeName isEqualToString: NSAccessibilitySizeAttribute]) { + IntSize s = m_object->size(); + return [NSValue valueWithSize: NSMakeSize(s.width(), s.height())]; + } + + if ([attributeName isEqualToString: NSAccessibilityPositionAttribute]) + return [self position]; + + if ([attributeName isEqualToString: NSAccessibilityWindowAttribute] || + [attributeName isEqualToString: NSAccessibilityTopLevelUIElementAttribute]) { + + id remoteParent = [self remoteAccessibilityParentObject]; + if (remoteParent) + return [remoteParent accessibilityAttributeValue:attributeName]; + + FrameView* fv = m_object->documentFrameView(); + if (fv) + return [fv->platformWidget() window]; + return nil; + } + + if ([attributeName isEqualToString:NSAccessibilityAccessKeyAttribute]) { + AtomicString accessKey = m_object->accessKey(); + if (accessKey.isNull()) + return nil; + 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); + } else if (m_object->isScrollView()) { + AccessibilityObject::AccessibilityChildrenVector children = m_object->children(); + + // A scrollView's contents are everything except the scroll bars. + AccessibilityObject::AccessibilityChildrenVector contents; + unsigned childrenSize = children.size(); + for (unsigned k = 0; k < childrenSize; ++k) { + if (!children[k]->isScrollbar()) + contents.append(children[k]); + } + return convertToNSArray(contents); + } + } + + if (m_object->isAccessibilityTable()) { + // TODO: distinguish between visible and non-visible rows + if ([attributeName isEqualToString:NSAccessibilityRowsAttribute] || + [attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute]) { + return convertToNSArray(static_cast<AccessibilityTable*>(m_object)->rows()); + } + // TODO: distinguish between visible and non-visible columns + if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute] || + [attributeName isEqualToString:NSAccessibilityVisibleColumnsAttribute]) { + return convertToNSArray(static_cast<AccessibilityTable*>(m_object)->columns()); + } + + if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy; + m_object->selectedChildren(selectedChildrenCopy); + return convertToNSArray(selectedChildrenCopy); + } + + // HTML tables don't support these + if ([attributeName isEqualToString:NSAccessibilitySelectedColumnsAttribute] || + [attributeName isEqualToString:NSAccessibilitySelectedCellsAttribute]) + return nil; + + if ([attributeName isEqualToString:(NSString *)kAXColumnHeaderUIElementsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector columnHeaders; + static_cast<AccessibilityTable*>(m_object)->columnHeaders(columnHeaders); + return convertToNSArray(columnHeaders); + } + + if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) { + AccessibilityObject* headerContainer = static_cast<AccessibilityTable*>(m_object)->headerContainer(); + if (headerContainer) + return headerContainer->wrapper(); + return nil; + } + + if ([attributeName isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector rowHeaders; + static_cast<AccessibilityTable*>(m_object)->rowHeaders(rowHeaders); + return convertToNSArray(rowHeaders); + } + + if ([attributeName isEqualToString:NSAccessibilityVisibleCellsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector cells; + static_cast<AccessibilityTable*>(m_object)->cells(cells); + return convertToNSArray(cells); + } + } + + if (m_object->isTableColumn()) { + if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) + return [NSNumber numberWithInt:static_cast<AccessibilityTableColumn*>(m_object)->columnIndex()]; + + // rows attribute for a column is the list of all the elements in that column at each row + if ([attributeName isEqualToString:NSAccessibilityRowsAttribute] || + [attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute]) { + return convertToNSArray(static_cast<AccessibilityTableColumn*>(m_object)->children()); + } + if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) { + AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_object)->headerObject(); + if (!header) + return nil; + return header->wrapper(); + } + } + + if (m_object->isTableCell()) { + if ([attributeName isEqualToString:NSAccessibilityRowIndexRangeAttribute]) { + pair<int, int> rowRange; + static_cast<AccessibilityTableCell*>(m_object)->rowIndexRange(rowRange); + return [NSValue valueWithRange:NSMakeRange(rowRange.first, rowRange.second)]; + } + if ([attributeName isEqualToString:NSAccessibilityColumnIndexRangeAttribute]) { + pair<int, int> columnRange; + static_cast<AccessibilityTableCell*>(m_object)->columnIndexRange(columnRange); + return [NSValue valueWithRange:NSMakeRange(columnRange.first, columnRange.second)]; + } + } + + 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 ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) { + if (m_object->isTreeItem()) { + 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; + } + if (m_object->isTableRow()) { + if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) + return [NSNumber numberWithInt:static_cast<AccessibilityTableRow*>(m_object)->rowIndex()]; + } + } + + // The rows that are considered inside this row. + if ([attributeName isEqualToString:NSAccessibilityDisclosedRowsAttribute]) { + if (m_object->isTreeItem()) { + AccessibilityObject::AccessibilityChildrenVector rowsCopy; + m_object->ariaTreeItemDisclosedRows(rowsCopy); + return convertToNSArray(rowsCopy); + } else if (m_object->isARIATreeGridRow()) { + AccessibilityObject::AccessibilityChildrenVector rowsCopy; + static_cast<AccessibilityARIAGridRow*>(m_object)->disclosedRows(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]) { + if (m_object->isTreeItem()) { + 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; + } else if (m_object->isARIATreeGridRow()) { + AccessibilityObject* row = static_cast<AccessibilityARIAGridRow*>(m_object)->disclosedByRow(); + if (!row) + return nil; + return row->wrapper(); + } + } + + 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; + + if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) + return [self textMarkerRangeForSelection]; + + if (m_object->isAccessibilityRenderObject()) { + RenderObject* renderer = static_cast<AccessibilityRenderObject*>(m_object)->renderer(); + if (!renderer) + return nil; + + if ([attributeName isEqualToString: @"AXStartTextMarker"]) + return [self textMarkerForVisiblePosition:startOfDocument(renderer->document())]; + if ([attributeName isEqualToString: @"AXEndTextMarker"]) + return [self textMarkerForVisiblePosition:endOfDocument(renderer->document())]; + + if ([attributeName isEqualToString:NSAccessibilityBlockQuoteLevelAttribute]) + return [NSNumber numberWithInt:blockquoteLevel(renderer)]; + } else { + if ([attributeName isEqualToString:NSAccessibilityBlockQuoteLevelAttribute]) { + AccessibilityObject* parent = m_object->parentObjectUnignored(); + if (!parent) + return [NSNumber numberWithInt:0]; + return [parent->wrapper() accessibilityAttributeValue:NSAccessibilityBlockQuoteLevelAttribute]; + } + } + + if ([attributeName isEqualToString: NSAccessibilityLinkedUIElementsAttribute]) { + AccessibilityObject::AccessibilityChildrenVector linkedUIElements; + m_object->linkedUIElements(linkedUIElements); + if (linkedUIElements.size() == 0) + return nil; + return convertToNSArray(linkedUIElements); + } + + if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) + return [NSNumber numberWithBool:m_object->isSelected()]; + + if ([attributeName isEqualToString: NSAccessibilityServesAsTitleForUIElementsAttribute] && m_object->isMenuButton()) { + AccessibilityObject* uiElement = static_cast<AccessibilityRenderObject*>(m_object)->menuForMenuButton(); + if (uiElement) + return [NSArray arrayWithObject:uiElement->wrapper()]; + } + + if ([attributeName isEqualToString:NSAccessibilityTitleUIElementAttribute]) { + AccessibilityObject* obj = m_object->titleUIElement(); + if (obj) + return obj->wrapper(); + return nil; + } + + if ([attributeName isEqualToString:NSAccessibilityValueDescriptionAttribute]) + return m_object->valueDescription(); + + if ([attributeName isEqualToString:NSAccessibilityOrientationAttribute]) { + AccessibilityOrientation elementOrientation = m_object->orientation(); + if (elementOrientation == AccessibilityOrientationVertical) + return NSAccessibilityVerticalOrientationValue; + if (elementOrientation == AccessibilityOrientationHorizontal) + return NSAccessibilityHorizontalOrientationValue; + return nil; + } + + if ([attributeName isEqualToString:NSAccessibilityHorizontalScrollBarAttribute]) { + AccessibilityObject* scrollBar = m_object->scrollBar(AccessibilityOrientationHorizontal); + if (scrollBar) + return scrollBar->wrapper(); + return nil; + } + if ([attributeName isEqualToString:NSAccessibilityVerticalScrollBarAttribute]) { + AccessibilityObject* scrollBar = m_object->scrollBar(AccessibilityOrientationVertical); + if (scrollBar) + return scrollBar->wrapper(); + return nil; + } + + 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:NSAccessibilityInvalidAttribute]) + return m_object->invalidStatus(); + + 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; + } + + if ([attributeName isEqualToString:NSAccessibilityPlaceholderValueAttribute]) + return m_object->placeholderValue(); + + if ([attributeName isEqualToString:NSAccessibilityHasPopupAttribute]) + return [NSNumber numberWithBool:m_object->ariaHasPopup()]; + + // ARIA Live region attributes. + if ([attributeName isEqualToString:NSAccessibilityARIALiveAttribute]) + return m_object->ariaLiveRegionStatus(); + if ([attributeName isEqualToString:NSAccessibilityARIARelevantAttribute]) + return m_object->ariaLiveRegionRelevant(); + if ([attributeName isEqualToString:NSAccessibilityARIAAtomicAttribute]) + return [NSNumber numberWithBool:m_object->ariaLiveRegionAtomic()]; + if ([attributeName isEqualToString:NSAccessibilityARIABusyAttribute]) + return [NSNumber numberWithBool:m_object->ariaLiveRegionBusy()]; + + // this is used only by DumpRenderTree for testing + if ([attributeName isEqualToString:@"AXClickPoint"]) + return [NSValue valueWithPoint:m_object->clickPoint()]; + + // This is used by DRT to verify CSS3 speech works. + if ([attributeName isEqualToString:@"AXDRTSpeechAttribute"]) { + ESpeak speakProperty = m_object->speakProperty(); + switch (speakProperty) { + case SpeakNone: + return @"none"; + case SpeakSpellOut: + return @"spell-out"; + case SpeakDigits: + return @"digits"; + case SpeakLiteralPunctuation: + return @"literal-punctuation"; + case SpeakNoPunctuation: + return @"no-punctuation"; + default: + case SpeakNormal: + return @"normal"; + } + } + + return nil; +} + +- (id)accessibilityFocusedUIElement +{ + if (![self updateObjectBackingStore]) + return nil; + + RefPtr<AccessibilityObject> focusedObj = m_object->focusedUIElement(); + + if (!focusedObj) + return nil; + + return focusedObj->wrapper(); +} + +- (id)accessibilityHitTest:(NSPoint)point +{ + if (![self updateObjectBackingStore]) + return nil; + + m_object->updateChildrenIfNecessary(); + RefPtr<AccessibilityObject> axObject = m_object->accessibilityHitTest(IntPoint(point)); + if (axObject) + return NSAccessibilityUnignoredAncestor(axObject->wrapper()); + return NSAccessibilityUnignoredAncestor(self); +} + +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attributeName +{ + if (![self updateObjectBackingStore]) + return nil; + + if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) + return YES; + + if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) + return m_object->canSetFocusAttribute(); + + if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) + return m_object->canSetValueAttribute(); + + if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) + return m_object->canSetSelectedAttribute(); + + 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; +} + +// accessibilityShouldUseUniqueId is an AppKit method we override so that +// objects will be given a unique ID, and therefore allow AppKit to know when they +// become obsolete (e.g. when the user navigates to a new web page, making this one +// unrendered but not deallocated because it is in the back/forward cache). +// It is important to call NSAccessibilityUnregisterUniqueIdForUIElement in the +// appropriate place (e.g. dealloc) to remove these non-retained references from +// AppKit's id mapping tables. We do this in detach by calling unregisterUniqueIdForUIElement. +// +// Registering an object is also required for observing notifications. Only registered objects can be observed. +- (BOOL)accessibilityIsIgnored +{ + if (![self updateObjectBackingStore]) + return YES; + + if (m_object->isAttachment()) + return [[self attachmentView] accessibilityIsIgnored]; + return m_object->accessibilityIsIgnored(); +} + +- (NSArray* )accessibilityParameterizedAttributeNames +{ + if (![self updateObjectBackingStore]) + return nil; + + if (m_object->isAttachment()) + return nil; + + static NSArray* paramAttrs = nil; + static NSArray* textParamAttrs = nil; + static NSArray* tableParamAttrs = nil; + if (paramAttrs == nil) { + paramAttrs = [[NSArray alloc] initWithObjects: + @"AXUIElementForTextMarker", + @"AXTextMarkerRangeForUIElement", + @"AXLineForTextMarker", + @"AXTextMarkerRangeForLine", + @"AXStringForTextMarkerRange", + @"AXTextMarkerForPosition", + @"AXBoundsForTextMarkerRange", + @"AXAttributedStringForTextMarkerRange", + @"AXTextMarkerRangeForUnorderedTextMarkers", + @"AXNextTextMarkerForTextMarker", + @"AXPreviousTextMarkerForTextMarker", + @"AXLeftWordTextMarkerRangeForTextMarker", + @"AXRightWordTextMarkerRangeForTextMarker", + @"AXLeftLineTextMarkerRangeForTextMarker", + @"AXRightLineTextMarkerRangeForTextMarker", + @"AXSentenceTextMarkerRangeForTextMarker", + @"AXParagraphTextMarkerRangeForTextMarker", + @"AXNextWordEndTextMarkerForTextMarker", + @"AXPreviousWordStartTextMarkerForTextMarker", + @"AXNextLineEndTextMarkerForTextMarker", + @"AXPreviousLineStartTextMarkerForTextMarker", + @"AXNextSentenceEndTextMarkerForTextMarker", + @"AXPreviousSentenceStartTextMarkerForTextMarker", + @"AXNextParagraphEndTextMarkerForTextMarker", + @"AXPreviousParagraphStartTextMarkerForTextMarker", + @"AXStyleTextMarkerRangeForTextMarker", + @"AXLengthForTextMarkerRange", + NSAccessibilityBoundsForRangeParameterizedAttribute, + NSAccessibilityStringForRangeParameterizedAttribute, + nil]; + } + + if (textParamAttrs == nil) { + NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs]; + [tempArray addObject:(NSString*)kAXLineForIndexParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXRangeForLineParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXStringForRangeParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXRangeForPositionParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXRangeForIndexParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXBoundsForRangeParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXRTFForRangeParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXAttributedStringForRangeParameterizedAttribute]; + [tempArray addObject:(NSString*)kAXStyleRangeForIndexParameterizedAttribute]; + textParamAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (tableParamAttrs == nil) { + NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs]; + [tempArray addObject:NSAccessibilityCellForColumnAndRowParameterizedAttribute]; + tableParamAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + if (m_object->isPasswordField()) + return [NSArray array]; + + if (!m_object->isAccessibilityRenderObject()) + return paramAttrs; + + if (m_object->isTextControl()) + return textParamAttrs; + + if (m_object->isAccessibilityTable()) + return tableParamAttrs; + + if (m_object->isMenuRelated()) + return nil; + + return paramAttrs; +} + +- (void)accessibilityPerformPressAction +{ + if (![self updateObjectBackingStore]) + return; + + if (m_object->isAttachment()) + [[self attachmentView] accessibilityPerformAction:NSAccessibilityPressAction]; + else + m_object->press(); +} + +- (void)accessibilityPerformIncrementAction +{ + if (![self updateObjectBackingStore]) + return; + + if (m_object->isAttachment()) + [[self attachmentView] accessibilityPerformAction:NSAccessibilityIncrementAction]; + else + m_object->increment(); +} + +- (void)accessibilityPerformDecrementAction +{ + if (![self updateObjectBackingStore]) + return; + + if (m_object->isAttachment()) + [[self attachmentView] accessibilityPerformAction:NSAccessibilityDecrementAction]; + else + m_object->decrement(); +} + +- (void)accessibilityPerformShowMenuAction +{ + 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 +{ + FrameView* frameView = m_object->documentFrameView(); + if (!frameView) + return; + + // simulate a click in the middle of the object + IntPoint clickPoint = m_object->clickPoint(); + NSPoint nsClickPoint = NSMakePoint(clickPoint.x(), clickPoint.y()); + + NSView* view = nil; + if (m_object->isAttachment()) + view = [self attachmentView]; + else + view = frameView->documentView(); + + if (!view) + return; + + NSPoint nsScreenPoint = [view convertPoint:nsClickPoint toView:nil]; + + // Show the contextual menu for this event. + NSEvent* event = [NSEvent mouseEventWithType:NSRightMouseDown location:nsScreenPoint modifierFlags:0 timestamp:0 windowNumber:[[view window] windowNumber] context:0 eventNumber:0 clickCount:1 pressure:1]; + NSMenu* menu = [view menuForEvent:event]; + + if (menu) + [NSMenu popUpContextMenu:menu withEvent:event forView:view]; +} + +- (void)accessibilityPerformAction:(NSString*)action +{ + if (![self updateObjectBackingStore]) + return; + + if ([action isEqualToString:NSAccessibilityPressAction]) + [self accessibilityPerformPressAction]; + + else if ([action isEqualToString:NSAccessibilityShowMenuAction]) + [self accessibilityPerformShowMenuAction]; + + else if ([action isEqualToString:NSAccessibilityIncrementAction]) + [self accessibilityPerformIncrementAction]; + + else if ([action isEqualToString:NSAccessibilityDecrementAction]) + [self accessibilityPerformDecrementAction]; +} + +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName +{ + if (![self updateObjectBackingStore]) + return; + + WebCoreTextMarkerRange* textMarkerRange = nil; + NSNumber* number = nil; + NSString* string = nil; + NSRange range = {0, 0}; + NSArray* array = nil; + + // decode the parameter + if ([[WebCoreViewFactory sharedFactory] objectIsTextMarkerRange:value]) + textMarkerRange = (WebCoreTextMarkerRange*) value; + + else if ([value isKindOfClass:[NSNumber self]]) + number = value; + + else if ([value isKindOfClass:[NSString self]]) + string = value; + + else if ([value isKindOfClass:[NSValue self]]) + range = [value rangeValue]; + + else if ([value isKindOfClass:[NSArray self]]) + array = value; + + // handle the command + if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) { + ASSERT(textMarkerRange); + m_object->setSelectedVisiblePositionRange([self visiblePositionRangeForTextMarkerRange:textMarkerRange]); + } else if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) { + ASSERT(number); + m_object->setFocused([number intValue] != 0); + } else if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) { + if (number && m_object->canSetNumericValue()) + m_object->setValue([number floatValue]); + else if (string) + m_object->setValue(string); + } else if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) { + if (!number) + return; + m_object->setSelected([number boolValue]); + } else if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) { + if (!array || m_object->roleValue() != ListBoxRole) + return; + AccessibilityObject::AccessibilityChildrenVector selectedChildren; + convertToVector(array, selectedChildren); + static_cast<AccessibilityListBox*>(m_object)->setSelectedChildren(selectedChildren); + } else if (m_object->isTextControl()) { + if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) { + m_object->setSelectedText(string); + } else if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) { + m_object->setSelectedTextRange(PlainTextRange(range.location, range.length)); + } 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->isAccessibilityTable()) + m_object->setSelectedRows(selectedRows); + } else if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) + m_object->setARIAGrabbed([number boolValue]); +} + +static RenderObject* rendererForView(NSView* view) +{ + if (![view conformsToProtocol:@protocol(WebCoreFrameView)]) + return 0; + + NSView<WebCoreFrameView>* frameView = (NSView<WebCoreFrameView>*)view; + Frame* frame = [frameView _web_frame]; + if (!frame) + return 0; + + Node* node = frame->document()->ownerElement(); + if (!node) + return 0; + + return node->renderer(); +} + +- (id)_accessibilityParentForSubview:(NSView*)subview +{ + RenderObject* renderer = rendererForView(subview); + if (!renderer) + return nil; + + AccessibilityObject* obj = renderer->document()->axObjectCache()->getOrCreate(renderer); + if (obj) + return obj->parentObjectUnignored()->wrapper(); + return nil; +} + +- (NSString*)accessibilityActionDescription:(NSString*)action +{ + // we have no custom actions + return NSAccessibilityActionDescription(action); +} + +// The CFAttributedStringType representation of the text associated with this accessibility +// object that is specified by the given range. +- (NSAttributedString*)doAXAttributedStringForRange:(NSRange)range +{ + PlainTextRange textRange = PlainTextRange(range.location, range.length); + VisiblePositionRange visiblePosRange = m_object->visiblePositionRangeForRange(textRange); + return [self doAXAttributedStringForTextMarkerRange:[self textMarkerRangeFromVisiblePositions:visiblePosRange.start endPosition:visiblePosRange.end]]; +} + +// The RTF representation of the text associated with this accessibility object that is +// specified by the given range. +- (NSData*)doAXRTFForRange:(NSRange)range +{ + NSAttributedString* attrString = [self doAXAttributedStringForRange:range]; + return [attrString RTFFromRange: NSMakeRange(0, [attrString length]) documentAttributes: nil]; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter +{ + WebCoreTextMarker* textMarker = nil; + WebCoreTextMarkerRange* textMarkerRange = nil; + NSNumber* number = nil; + NSArray* array = nil; + RefPtr<AccessibilityObject> uiElement = 0; + NSPoint point = NSZeroPoint; + bool pointSet = false; + NSRange range = {0, 0}; + bool rangeSet = false; + + // basic parameter validation + if (!m_object || !attribute || !parameter) + return nil; + + if (![self updateObjectBackingStore]) + return nil; + + // common parameter type check/casting. Nil checks in handlers catch wrong type case. + // NOTE: This assumes nil is not a valid parameter, because it is indistinguishable from + // a parameter of the wrong type. + if ([[WebCoreViewFactory sharedFactory] objectIsTextMarker:parameter]) + textMarker = (WebCoreTextMarker*) parameter; + + else if ([[WebCoreViewFactory sharedFactory] objectIsTextMarkerRange:parameter]) + textMarkerRange = (WebCoreTextMarkerRange*) parameter; + + else if ([parameter isKindOfClass:[AccessibilityObjectWrapper self]]) + uiElement = [(AccessibilityObjectWrapper*)parameter accessibilityObject]; + + else if ([parameter isKindOfClass:[NSNumber self]]) + number = parameter; + + else if ([parameter isKindOfClass:[NSArray self]]) + array = parameter; + + else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSPoint)) == 0) { + pointSet = true; + point = [(NSValue*)parameter pointValue]; + + } else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSRange)) == 0) { + rangeSet = true; + range = [(NSValue*)parameter rangeValue]; + } else { + // Attribute type is not supported. Allow super to handle. + return [super accessibilityAttributeValue:attribute forParameter:parameter]; + } + + // dispatch + if ([attribute isEqualToString:@"AXUIElementForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + AccessibilityObject* axObject = m_object->accessibilityObjectForPosition(visiblePos); + if (!axObject) + return nil; + return axObject->wrapper(); + } + + if ([attribute isEqualToString:@"AXTextMarkerRangeForUIElement"]) { + VisiblePositionRange vpRange = uiElement.get()->visiblePositionRange(); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXLineForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [NSNumber numberWithUnsignedInt:m_object->lineForPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXTextMarkerRangeForLine"]) { + VisiblePositionRange vpRange = m_object->visiblePositionRangeForLine([number intValue]); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXStringForTextMarkerRange"]) { + VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; + return m_object->stringForVisiblePositionRange(visiblePosRange); + } + + if ([attribute isEqualToString:@"AXTextMarkerForPosition"]) { + IntPoint webCorePoint = IntPoint(point); + return pointSet ? [self textMarkerForVisiblePosition:m_object->visiblePositionForPoint(webCorePoint)] : nil; + } + + if ([attribute isEqualToString:@"AXBoundsForTextMarkerRange"]) { + VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; + NSRect rect = m_object->boundsForVisiblePositionRange(visiblePosRange); + return [NSValue valueWithRect:rect]; + } + + if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) { + VisiblePosition start = m_object->visiblePositionForIndex(range.location); + VisiblePosition end = m_object->visiblePositionForIndex(range.location+range.length); + if (start.isNull() || end.isNull()) + return nil; + NSRect rect = m_object->boundsForVisiblePositionRange(VisiblePositionRange(start, end)); + return [NSValue valueWithRect:rect]; + } + + if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) { + VisiblePosition start = m_object->visiblePositionForIndex(range.location); + VisiblePosition end = m_object->visiblePositionForIndex(range.location+range.length); + if (start.isNull() || end.isNull()) + return nil; + return m_object->stringForVisiblePositionRange(VisiblePositionRange(start, end)); + } + + if ([attribute isEqualToString:@"AXAttributedStringForTextMarkerRange"]) + return [self doAXAttributedStringForTextMarkerRange:textMarkerRange]; + + if ([attribute isEqualToString:@"AXTextMarkerRangeForUnorderedTextMarkers"]) { + if ([array count] < 2) + return nil; + + WebCoreTextMarker* textMarker1 = (WebCoreTextMarker*) [array objectAtIndex:0]; + WebCoreTextMarker* textMarker2 = (WebCoreTextMarker*) [array objectAtIndex:1]; + if (![[WebCoreViewFactory sharedFactory] objectIsTextMarker:textMarker1] + || ![[WebCoreViewFactory sharedFactory] objectIsTextMarker:textMarker2]) + return nil; + + VisiblePosition visiblePos1 = [self visiblePositionForTextMarker:(textMarker1)]; + VisiblePosition visiblePos2 = [self visiblePositionForTextMarker:(textMarker2)]; + VisiblePositionRange vpRange = m_object->visiblePositionRangeForUnorderedPositions(visiblePos1, visiblePos2); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXNextTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->nextVisiblePosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXPreviousTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->previousVisiblePosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXLeftWordTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->positionOfLeftWord(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXRightWordTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->positionOfRightWord(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXLeftLineTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->leftLineVisiblePositionRange(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXRightLineTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->rightLineVisiblePositionRange(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXSentenceTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->sentenceForPosition(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXParagraphTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->paragraphForPosition(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXNextWordEndTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->nextWordEnd(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXPreviousWordStartTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->previousWordStart(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXNextLineEndTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->nextLineEndPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXPreviousLineStartTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->previousLineStartPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXNextSentenceEndTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->nextSentenceEndPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXPreviousSentenceStartTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->previousSentenceStartPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXNextParagraphEndTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->nextParagraphEndPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXPreviousParagraphStartTextMarkerForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + return [self textMarkerForVisiblePosition:m_object->previousParagraphStartPosition(visiblePos)]; + } + + if ([attribute isEqualToString:@"AXStyleTextMarkerRangeForTextMarker"]) { + VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; + VisiblePositionRange vpRange = m_object->styleRangeForPosition(visiblePos); + return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; + } + + if ([attribute isEqualToString:@"AXLengthForTextMarkerRange"]) { + VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; + int length = m_object->lengthForVisiblePositionRange(visiblePosRange); + if (length < 0) + return nil; + return [NSNumber numberWithInt:length]; + } + + // Used only by DumpRenderTree (so far). + if ([attribute isEqualToString:@"AXStartTextMarkerForTextMarkerRange"]) { + VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; + return [self textMarkerForVisiblePosition:visiblePosRange.start]; + } + + if ([attribute isEqualToString:@"AXEndTextMarkerForTextMarkerRange"]) { + VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; + return [self textMarkerForVisiblePosition:visiblePosRange.end]; + } + + if (m_object->isAccessibilityTable()) { + if ([attribute isEqualToString:NSAccessibilityCellForColumnAndRowParameterizedAttribute]) { + if (array == nil || [array count] != 2) + return nil; + AccessibilityTableCell* cell = static_cast<AccessibilityTable*>(m_object)->cellForColumnAndRow([[array objectAtIndex:0] unsignedIntValue], [[array objectAtIndex:1] unsignedIntValue]); + if (!cell) + return nil; + + return cell->wrapper(); + } + } + + if (m_object->isTextControl()) { + if ([attribute isEqualToString: (NSString *)kAXLineForIndexParameterizedAttribute]) { + int lineNumber = m_object->doAXLineForIndex([number intValue]); + if (lineNumber < 0) + return nil; + return [NSNumber numberWithUnsignedInt:lineNumber]; + } + + if ([attribute isEqualToString: (NSString *)kAXRangeForLineParameterizedAttribute]) { + PlainTextRange textRange = m_object->doAXRangeForLine([number intValue]); + return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; + } + + if ([attribute isEqualToString: (NSString*)kAXStringForRangeParameterizedAttribute]) { + PlainTextRange plainTextRange = PlainTextRange(range.location, range.length); + return rangeSet ? (id)(m_object->doAXStringForRange(plainTextRange)) : nil; + } + + if ([attribute isEqualToString: (NSString*)kAXRangeForPositionParameterizedAttribute]) { + if (!pointSet) + return nil; + IntPoint webCorePoint = IntPoint(point); + PlainTextRange textRange = m_object->doAXRangeForPosition(webCorePoint); + return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; + } + + if ([attribute isEqualToString: (NSString*)kAXRangeForIndexParameterizedAttribute]) { + PlainTextRange textRange = m_object->doAXRangeForIndex([number intValue]); + return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; + } + + if ([attribute isEqualToString: (NSString*)kAXBoundsForRangeParameterizedAttribute]) { + if (!rangeSet) + return nil; + PlainTextRange plainTextRange = PlainTextRange(range.location, range.length); + NSRect rect = m_object->doAXBoundsForRange(plainTextRange); + return [NSValue valueWithRect:rect]; + } + + if ([attribute isEqualToString: (NSString*)kAXRTFForRangeParameterizedAttribute]) + return rangeSet ? [self doAXRTFForRange:range] : nil; + + if ([attribute isEqualToString: (NSString*)kAXAttributedStringForRangeParameterizedAttribute]) + return rangeSet ? [self doAXAttributedStringForRange:range] : nil; + + if ([attribute isEqualToString: (NSString*)kAXStyleRangeForIndexParameterizedAttribute]) { + PlainTextRange textRange = m_object->doAXStyleRangeForIndex([number intValue]); + return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; + } + } + + // There are some parameters that super handles that are not explicitly returned by the list of the element's attributes. + // In that case it must be passed to super. + return [super accessibilityAttributeValue:attribute forParameter:parameter]; +} + +- (BOOL)accessibilityShouldUseUniqueId +{ + return m_object->accessibilityShouldUseUniqueId(); +} + +// API that AppKit uses for faster access +- (NSUInteger)accessibilityIndexOfChild:(id)child +{ + if (![self updateObjectBackingStore]) + 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()) + return [[self renderWidgetChildren] indexOfObject:child]; + + unsigned count = children.size(); + for (unsigned k = 0; k < count; ++k) { + AccessibilityObjectWrapper* wrapper = children[k]->wrapper(); + if (wrapper == child || (children[k]->isAttachment() && [wrapper attachmentView] == child)) + return k; + } + + return NSNotFound; +} + +- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute +{ + if (![self updateObjectBackingStore]) + 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]; + + return children.size(); + } + + return [super accessibilityArrayAttributeCount:attribute]; +} + +- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount +{ + if (![self updateObjectBackingStore]) + return nil; + + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + if (m_object->children().isEmpty()) { + NSArray *children = [self renderWidgetChildren]; + if (!children) + return nil; + + NSUInteger childCount = [children count]; + if (index >= childCount) + return nil; + + 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(); + unsigned childCount = children.size(); + if (index >= childCount) + return nil; + + unsigned available = min(childCount - index, maxCount); + + NSMutableArray *subarray = [NSMutableArray arrayWithCapacity:available]; + for (unsigned added = 0; added < available; ++index, ++added) { + AccessibilityObjectWrapper* wrapper = children[index]->wrapper(); + if (wrapper) { + // The attachment view should be returned, otherwise AX palindrome errors occur. + if (children[index]->isAttachment() && [wrapper attachmentView]) + [subarray addObject:[wrapper attachmentView]]; + else + [subarray addObject:wrapper]; + } + } + + return subarray; + } + + return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; +} + +// This is set by DRT when it wants to listen for notifications. +static BOOL accessibilityShouldRepostNotifications; +- (void)accessibilitySetShouldRepostNotifications:(BOOL)repost +{ + accessibilityShouldRepostNotifications = repost; +} + +- (void)accessibilityPostedNotification:(NSString *)notificationName +{ + if (accessibilityShouldRepostNotifications) { + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"AXDRTNotification" object:nil userInfo:userInfo]; + } +} + +@end + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/qt/AccessibilityObjectQt.cpp b/Source/WebCore/accessibility/qt/AccessibilityObjectQt.cpp new file mode 100644 index 0000000..7232642 --- /dev/null +++ b/Source/WebCore/accessibility/qt/AccessibilityObjectQt.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Apple Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObject.h" + +#if HAVE(ACCESSIBILITY) + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + if (isMenuListPopup() || isMenuListOption()) + return IgnoreObject; + + return DefaultBehavior; +} + +} // namespace WebCore + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/win/AXObjectCacheWin.cpp b/Source/WebCore/accessibility/win/AXObjectCacheWin.cpp new file mode 100644 index 0000000..b20b51a --- /dev/null +++ b/Source/WebCore/accessibility/win/AXObjectCacheWin.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "config.h" +#include "AXObjectCache.h" + +#include "AccessibilityObject.h" +#include "Chrome.h" +#include "Document.h" +#include "Page.h" +#include "RenderObject.h" + +using namespace std; + +namespace WebCore { + +void AXObjectCache::detachWrapper(AccessibilityObject* obj) +{ + // On Windows, AccessibilityObjects are created when get_accChildCount is + // called, but they are not wrapped until get_accChild is called, so this + // object may not have a wrapper. + if (AccessibilityObjectWrapper* wrapper = obj->wrapper()) + wrapper->detach(); +} + +void AXObjectCache::attachWrapper(AccessibilityObject*) +{ + // On Windows, AccessibilityObjects are wrapped when the accessibility + // software requests them via get_accChild. +} + +void AXObjectCache::handleScrolledToAnchor(const Node* anchorNode) +{ + // The anchor node may not be accessible. Post the notification for the + // first accessible object. + postPlatformNotification(AccessibilityObject::firstAccessibleObjectFromNode(anchorNode), AXScrolledToAnchor); +} + +void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotification notification) +{ + if (!obj) + return; + + Document* document = obj->document(); + if (!document) + return; + + Page* page = document->page(); + if (!page || !page->chrome()->platformPageClient()) + return; + + DWORD msaaEvent; + switch (notification) { + case AXFocusedUIElementChanged: + case AXActiveDescendantChanged: + msaaEvent = EVENT_OBJECT_FOCUS; + break; + + case AXScrolledToAnchor: + msaaEvent = EVENT_SYSTEM_SCROLLINGSTART; + break; + + case AXValueChanged: + case AXMenuListValueChanged: + msaaEvent = EVENT_OBJECT_VALUECHANGE; + break; + + default: + return; + } + + // Windows will end up calling get_accChild() on the root accessible + // object for the WebView, passing the child ID that we specify below. We + // negate the AXID so we know that the caller is passing the ID of an + // element, not the index of a child element. + + ASSERT(obj->axObjectID() >= 1); + ASSERT(obj->axObjectID() <= numeric_limits<LONG>::max()); + + NotifyWinEvent(msaaEvent, page->chrome()->platformPageClient(), OBJID_CLIENT, -static_cast<LONG>(obj->axObjectID())); +} + +void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, unsigned) +{ +} + +AXID AXObjectCache::platformGenerateAXID() const +{ + static AXID lastUsedID = 0; + + // Generate a new ID. Windows accessibility relies on a positive AXID, + // ranging from 1 to LONG_MAX. + AXID objID = lastUsedID; + do { + ++objID; + objID %= std::numeric_limits<LONG>::max(); + } while (objID == 0 || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); + + ASSERT(objID >= 1 && objID <= std::numeric_limits<LONG>::max()); + + lastUsedID = objID; + + return objID; +} + +void AXObjectCache::handleFocusedUIElementChanged(RenderObject*, RenderObject* newFocusedRenderer) +{ + if (!newFocusedRenderer) + return; + + Page* page = newFocusedRenderer->document()->page(); + if (!page || !page->chrome()->platformPageClient()) + return; + + AccessibilityObject* focusedObject = focusedUIElementForPage(page); + if (!focusedObject) + return; + + ASSERT(!focusedObject->accessibilityIsIgnored()); + + postPlatformNotification(focusedObject, AXFocusedUIElementChanged); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/win/AccessibilityObjectWin.cpp b/Source/WebCore/accessibility/win/AccessibilityObjectWin.cpp new file mode 100644 index 0000000..44122ef --- /dev/null +++ b/Source/WebCore/accessibility/win/AccessibilityObjectWin.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityObject.h" + +#if HAVE(ACCESSIBILITY) + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + if (isMenuListPopup() || isMenuListOption()) + return IncludeObject; + + return DefaultBehavior; +} + +} // namespace WebCore + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.h b/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.h new file mode 100644 index 0000000..779443c --- /dev/null +++ b/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef AccessibilityObjectWrapperWin_h +#define AccessibilityObjectWrapperWin_h + +namespace WebCore { + + class AccessibilityObject; + + class AccessibilityObjectWrapper : public IUnknown { + public: + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) = 0; + virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; + virtual ULONG STDMETHODCALLTYPE Release(void) = 0; + + virtual void detach() = 0; + bool attached() const { return m_object; } + AccessibilityObject* accessibilityObject() const { return m_object; } + + protected: + AccessibilityObjectWrapper(AccessibilityObject* obj) : m_object(obj) { } + AccessibilityObjectWrapper() : m_object(0) { } + + AccessibilityObject* m_object; + }; + +} // namespace WebCore + +#endif // AccessibilityObjectWrapperWin_h diff --git a/Source/WebCore/accessibility/wx/AccessibilityObjectWx.cpp b/Source/WebCore/accessibility/wx/AccessibilityObjectWx.cpp new file mode 100644 index 0000000..7232642 --- /dev/null +++ b/Source/WebCore/accessibility/wx/AccessibilityObjectWx.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Apple Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObject.h" + +#if HAVE(ACCESSIBILITY) + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + if (isMenuListPopup() || isMenuListOption()) + return IgnoreObject; + + return DefaultBehavior; +} + +} // namespace WebCore + +#endif // HAVE(ACCESSIBILITY) |