2 * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #if HAVE(ACCESSIBILITY)
33 #include "AXObjectCache.h"
35 #include "AccessibilityARIAGrid.h"
36 #include "AccessibilityARIAGridCell.h"
37 #include "AccessibilityARIAGridRow.h"
38 #include "AccessibilityImageMapLink.h"
39 #include "AccessibilityList.h"
40 #include "AccessibilityListBox.h"
41 #include "AccessibilityListBoxOption.h"
42 #include "AccessibilityMediaControls.h"
43 #include "AccessibilityMenuList.h"
44 #include "AccessibilityMenuListOption.h"
45 #include "AccessibilityMenuListPopup.h"
46 #include "AccessibilityProgressIndicator.h"
47 #include "AccessibilityRenderObject.h"
48 #include "AccessibilityScrollView.h"
49 #include "AccessibilityScrollbar.h"
50 #include "AccessibilitySlider.h"
51 #include "AccessibilitySpinButton.h"
52 #include "AccessibilityTable.h"
53 #include "AccessibilityTableCell.h"
54 #include "AccessibilityTableColumn.h"
55 #include "AccessibilityTableHeaderContainer.h"
56 #include "AccessibilityTableRow.h"
58 #include "FocusController.h"
60 #include "HTMLAreaElement.h"
61 #include "HTMLImageElement.h"
62 #include "HTMLInputElement.h"
63 #include "HTMLNames.h"
65 #include "MediaControlElements.h"
68 #include "RenderListBox.h"
69 #include "RenderMenuList.h"
70 #include "RenderProgress.h"
71 #include "RenderSlider.h"
72 #include "RenderTable.h"
73 #include "RenderTableCell.h"
74 #include "RenderTableRow.h"
75 #include "RenderView.h"
76 #include "ScrollView.h"
78 #include <wtf/PassRefPtr.h>
82 using namespace HTMLNames;
84 bool AXObjectCache::gAccessibilityEnabled = false;
85 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
87 AXObjectCache::AXObjectCache(const Document* doc)
88 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
90 m_document = const_cast<Document*>(doc);
93 AXObjectCache::~AXObjectCache()
95 HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end();
96 for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) {
97 AccessibilityObject* obj = (*it).second.get();
104 AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
106 // Find the corresponding accessibility object for the HTMLAreaElement. This should be
107 // in the list of children for its corresponding image.
111 HTMLImageElement* imageElement = areaElement->imageElement();
115 AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer());
119 AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children();
120 unsigned count = imageChildren.size();
121 for (unsigned k = 0; k < count; ++k) {
122 AccessibilityObject* child = imageChildren[k].get();
123 if (!child->isImageMapLink())
126 if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement)
133 AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
135 if (!gAccessibilityEnabled)
138 // get the focused node in the page
139 Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document();
140 Node* focusedNode = focusedDocument->focusedNode();
142 focusedNode = focusedDocument;
144 if (focusedNode->hasTagName(areaTag))
145 return focusedImageMapUIElement(static_cast<HTMLAreaElement*>(focusedNode));
147 RenderObject* focusedNodeRenderer = focusedNode->renderer();
148 if (!focusedNodeRenderer)
151 AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer);
153 if (obj->shouldFocusActiveDescendant()) {
154 if (AccessibilityObject* descendant = obj->activeDescendant())
158 // the HTML element, for example, is focusable but has an AX object that is ignored
159 if (obj->accessibilityIsIgnored())
160 obj = obj->parentObjectUnignored();
165 AccessibilityObject* AXObjectCache::get(Widget* widget)
170 AXID axID = m_widgetObjectMapping.get(widget);
171 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
175 return m_objects.get(axID).get();
178 AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
183 AXID axID = m_renderObjectMapping.get(renderer);
184 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
188 return m_objects.get(axID).get();
191 AccessibilityObject* AXObjectCache::get(Node* node)
196 // Always prefer building the AccessibilityObject from the renderer if there is one.
197 if (node->renderer())
198 return get(node->renderer());
200 AXID axID = m_nodeObjectMapping.get(node);
201 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
205 return m_objects.get(axID).get();
208 // FIXME: This probably belongs on Node.
209 // FIXME: This should take a const char*, but one caller passes nullAtom.
210 bool nodeHasRole(Node* node, const String& role)
212 if (!node || !node->isElementNode())
215 return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role);
218 static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer)
220 // FIXME: How could renderer->node() ever not be an Element?
221 Node* node = renderer->node();
223 // If the node is aria role="list" or the aria role is empty and its a
224 // ul/ol/dl type (it shouldn't be a list if aria says otherwise).
225 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory"))
226 || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
227 return AccessibilityList::create(renderer);
230 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid"))
231 return AccessibilityARIAGrid::create(renderer);
232 if (nodeHasRole(node, "row"))
233 return AccessibilityARIAGridRow::create(renderer);
234 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
235 return AccessibilityARIAGridCell::create(renderer);
239 if (node && node->isMediaControlElement())
240 return AccessibilityMediaControl::create(renderer);
243 if (renderer->isBoxModelObject()) {
244 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer);
245 if (cssBox->isListBox())
246 return AccessibilityListBox::create(toRenderListBox(cssBox));
247 if (cssBox->isMenuList())
248 return AccessibilityMenuList::create(toRenderMenuList(cssBox));
251 if (cssBox->isTable())
252 return AccessibilityTable::create(toRenderTable(cssBox));
253 if (cssBox->isTableRow())
254 return AccessibilityTableRow::create(toRenderTableRow(cssBox));
255 if (cssBox->isTableCell())
256 return AccessibilityTableCell::create(toRenderTableCell(cssBox));
258 #if ENABLE(PROGRESS_ELEMENT)
260 if (cssBox->isProgress())
261 return AccessibilityProgressIndicator::create(toRenderProgress(cssBox));
265 if (cssBox->isSlider())
266 return AccessibilitySlider::create(toRenderSlider(cssBox));
269 return AccessibilityRenderObject::create(renderer);
272 static PassRefPtr<AccessibilityObject> createFromNode(Node* node)
274 return AccessibilityNodeObject::create(node);
277 AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
282 if (AccessibilityObject* obj = get(widget))
285 RefPtr<AccessibilityObject> newObj = 0;
286 if (widget->isFrameView())
287 newObj = AccessibilityScrollView::create(static_cast<ScrollView*>(widget));
288 else if (widget->isScrollbar())
289 newObj = AccessibilityScrollbar::create(static_cast<Scrollbar*>(widget));
291 getAXID(newObj.get());
293 m_widgetObjectMapping.set(widget, newObj->axObjectID());
294 m_objects.set(newObj->axObjectID(), newObj);
295 attachWrapper(newObj.get());
299 AccessibilityObject* AXObjectCache::getOrCreate(Node* node)
304 if (AccessibilityObject* obj = get(node))
307 if (node->renderer())
308 return getOrCreate(node->renderer());
310 // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree.
311 if (!node->parentElement() || !node->parentElement()->isInCanvasSubtree())
314 RefPtr<AccessibilityObject> newObj = createFromNode(node);
316 getAXID(newObj.get());
318 m_nodeObjectMapping.set(node, newObj->axObjectID());
319 m_objects.set(newObj->axObjectID(), newObj);
320 attachWrapper(newObj.get());
324 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
329 if (AccessibilityObject* obj = get(renderer))
332 RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer);
334 getAXID(newObj.get());
336 m_renderObjectMapping.set(renderer, newObj->axObjectID());
337 m_objects.set(newObj->axObjectID(), newObj);
338 attachWrapper(newObj.get());
342 AccessibilityObject* AXObjectCache::rootObject()
344 if (!gAccessibilityEnabled)
347 return getOrCreate(m_document->view());
350 AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame)
352 if (!gAccessibilityEnabled)
357 return getOrCreate(frame->view());
360 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
362 RefPtr<AccessibilityObject> obj = 0;
364 // will be filled in...
366 case ListBoxOptionRole:
367 obj = AccessibilityListBoxOption::create();
369 case ImageMapLinkRole:
370 obj = AccessibilityImageMapLink::create();
373 obj = AccessibilityTableColumn::create();
375 case TableHeaderContainerRole:
376 obj = AccessibilityTableHeaderContainer::create();
378 case SliderThumbRole:
379 obj = AccessibilitySliderThumb::create();
381 case MenuListPopupRole:
382 obj = AccessibilityMenuListPopup::create();
384 case MenuListOptionRole:
385 obj = AccessibilityMenuListOption::create();
388 obj = AccessibilitySpinButton::create();
390 case SpinButtonPartRole:
391 obj = AccessibilitySpinButtonPart::create();
402 m_objects.set(obj->axObjectID(), obj);
403 attachWrapper(obj.get());
407 void AXObjectCache::remove(AXID axID)
412 // first fetch object to operate some cleanup functions on it
413 AccessibilityObject* obj = m_objects.get(axID).get();
421 // finally remove the object
422 if (!m_objects.take(axID))
425 ASSERT(m_objects.size() >= m_idsInUse.size());
428 void AXObjectCache::remove(RenderObject* renderer)
433 AXID axID = m_renderObjectMapping.get(renderer);
435 m_renderObjectMapping.remove(renderer);
438 void AXObjectCache::remove(Node* node)
443 removeNodeForUse(node);
445 // This is all safe even if we didn't have a mapping.
446 AXID axID = m_nodeObjectMapping.get(node);
448 m_nodeObjectMapping.remove(node);
450 if (node->renderer()) {
451 remove(node->renderer());
456 void AXObjectCache::remove(Widget* view)
461 AXID axID = m_widgetObjectMapping.get(view);
463 m_widgetObjectMapping.remove(view);
467 #if !PLATFORM(WIN) || OS(WINCE)
468 AXID AXObjectCache::platformGenerateAXID() const
470 static AXID lastUsedID = 0;
472 // Generate a new ID.
473 AXID objID = lastUsedID;
476 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
484 AXID AXObjectCache::getAXID(AccessibilityObject* obj)
486 // check for already-assigned ID
487 AXID objID = obj->axObjectID();
489 ASSERT(m_idsInUse.contains(objID));
493 objID = platformGenerateAXID();
495 m_idsInUse.add(objID);
496 obj->setAXObjectID(objID);
501 void AXObjectCache::removeAXID(AccessibilityObject* object)
506 AXID objID = object->axObjectID();
509 ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
510 ASSERT(m_idsInUse.contains(objID));
511 object->setAXObjectID(0);
512 m_idsInUse.remove(objID);
515 void AXObjectCache::contentChanged(RenderObject* renderer)
517 AccessibilityObject* object = getOrCreate(renderer);
519 object->contentChanged();
522 void AXObjectCache::childrenChanged(RenderObject* renderer)
527 AXID axID = m_renderObjectMapping.get(renderer);
531 AccessibilityObject* obj = m_objects.get(axID).get();
533 obj->childrenChanged();
536 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
538 m_notificationPostTimer.stop();
540 unsigned i = 0, count = m_notificationsToPost.size();
541 for (i = 0; i < count; ++i) {
542 AccessibilityObject* obj = m_notificationsToPost[i].first.get();
544 // Make sure none of the render views are in the process of being layed out.
545 // Notifications should only be sent after the renderer has finished
546 if (obj->isAccessibilityRenderObject()) {
547 AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj);
548 RenderObject* renderer = renderObj->renderer();
549 if (renderer && renderer->view())
550 ASSERT(!renderer->view()->layoutState());
554 postPlatformNotification(obj, m_notificationsToPost[i].second);
557 m_notificationsToPost.clear();
560 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType)
562 // Notifications for text input objects are sent to that object.
563 // All others are sent to the top WebArea.
567 // Get an accessibility object that already exists. One should not be created here
568 // because a render update may be in progress and creating an AX object can re-trigger a layout
569 RefPtr<AccessibilityObject> object = get(renderer);
570 while (!object && renderer) {
571 renderer = renderer->parent();
572 object = get(renderer);
578 postNotification(object.get(), renderer->document(), notification, postToElement, postType);
581 void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType)
583 if (object && !postToElement)
584 object = object->observableObject();
586 if (!object && document)
587 object = get(document->renderer());
592 if (postType == PostAsynchronously) {
593 m_notificationsToPost.append(std::make_pair(object, notification));
594 if (!m_notificationPostTimer.isActive())
595 m_notificationPostTimer.startOneShot(0);
597 postPlatformNotification(object, notification);
600 void AXObjectCache::checkedStateChanged(RenderObject* renderer)
602 postNotification(renderer, AXObjectCache::AXCheckedStateChanged, true);
605 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
607 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree
608 // to find the container which should send out the notification.
609 postNotification(renderer, AXSelectedChildrenChanged, false);
612 void AXObjectCache::nodeTextChangeNotification(RenderObject* renderer, AXTextChange textChange, unsigned offset, const String& text)
617 // Delegate on the right platform
618 AccessibilityObject* obj = getOrCreate(renderer);
619 nodeTextChangePlatformNotification(obj, textChange, offset, text);
622 void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent loadingEvent)
627 // Delegate on the right platform
628 RenderView* contentRenderer = frame->contentRenderer();
629 if (!contentRenderer)
632 AccessibilityObject* obj = getOrCreate(contentRenderer);
633 frameLoadingEventPlatformNotification(obj, loadingEvent);
636 void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
641 // We don't want to create a scroll view from this method, only update an existing one.
642 AccessibilityObject* scrollViewObject = get(view);
643 if (scrollViewObject)
644 scrollViewObject->updateChildrenIfNecessary();
647 void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer)
651 AccessibilityObject* obj = getOrCreate(renderer);
653 obj->handleAriaExpandedChanged();
656 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer)
660 AccessibilityObject* obj = getOrCreate(renderer);
662 obj->handleActiveDescendantChanged();
665 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer)
669 AccessibilityObject* obj = getOrCreate(renderer);
670 if (obj && obj->isAccessibilityRenderObject())
671 static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole();
674 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
676 if (!isNodeInUse(textMarkerData.node))
677 return VisiblePosition();
679 // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects.
680 VisiblePosition visiblePos = VisiblePosition(createLegacyEditingPosition(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity);
681 Position deepPos = visiblePos.deepEquivalent();
682 if (deepPos.isNull())
683 return VisiblePosition();
685 RenderObject* renderer = deepPos.deprecatedNode()->renderer();
687 return VisiblePosition();
689 AXObjectCache* cache = renderer->document()->axObjectCache();
690 if (!cache->isIDinUse(textMarkerData.axID))
691 return VisiblePosition();
693 if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
694 return VisiblePosition();
699 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
701 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
702 // This also allows callers to check for failure by looking at textMarkerData upon return.
703 memset(&textMarkerData, 0, sizeof(TextMarkerData));
705 if (visiblePos.isNull())
708 Position deepPos = visiblePos.deepEquivalent();
709 Node* domNode = deepPos.deprecatedNode();
714 if (domNode->isHTMLElement()) {
715 HTMLInputElement* inputElement = domNode->toInputElement();
716 if (inputElement && inputElement->isPasswordField())
720 // locate the renderer, which must exist for a visible dom node
721 RenderObject* renderer = domNode->renderer();
724 // find or create an accessibility object for this renderer
725 AXObjectCache* cache = renderer->document()->axObjectCache();
726 RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer);
728 textMarkerData.axID = obj.get()->axObjectID();
729 textMarkerData.node = domNode;
730 textMarkerData.offset = deepPos.deprecatedEditingOffset();
731 textMarkerData.affinity = visiblePos.affinity();
733 cache->setNodeInUse(domNode);
736 const Element* AXObjectCache::rootAXEditableElement(const Node* node)
738 const Element* result = node->rootEditableElement();
739 const Element* element = node->isElementNode() ? toElement(node) : node->parentElement();
741 for (; element; element = element->parentElement()) {
742 if (nodeIsTextControl(element))
749 bool AXObjectCache::nodeIsTextControl(const Node* node)
754 const AccessibilityObject* axObject = getOrCreate(node->renderer());
755 return axObject && axObject->isTextControl();
758 } // namespace WebCore
760 #endif // HAVE(ACCESSIBILITY)