WK2: Support Accessibility
[WebKit-https.git] / WebCore / accessibility / AXObjectCache.cpp
1 /*
2  * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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.
16  *
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.
27  */
28
29 #include "config.h"
30 #include "AXObjectCache.h"
31
32 #include "AccessibilityARIAGrid.h"
33 #include "AccessibilityARIAGridCell.h"
34 #include "AccessibilityARIAGridRow.h"
35 #include "AccessibilityImageMapLink.h"
36 #include "AccessibilityList.h"
37 #include "AccessibilityListBox.h"
38 #include "AccessibilityListBoxOption.h"
39 #include "AccessibilityMediaControls.h"
40 #include "AccessibilityMenuList.h"
41 #include "AccessibilityMenuListOption.h"
42 #include "AccessibilityMenuListPopup.h"
43 #include "AccessibilityProgressIndicator.h"
44 #include "AccessibilityRenderObject.h"
45 #include "AccessibilityScrollView.h"
46 #include "AccessibilityScrollbar.h"
47 #include "AccessibilitySlider.h"
48 #include "AccessibilityTable.h"
49 #include "AccessibilityTableCell.h"
50 #include "AccessibilityTableColumn.h"
51 #include "AccessibilityTableHeaderContainer.h"
52 #include "AccessibilityTableRow.h"
53 #include "Document.h"
54 #include "FocusController.h"
55 #include "Frame.h"
56 #include "HTMLAreaElement.h"
57 #include "HTMLImageElement.h"
58 #include "HTMLNames.h"
59 #if ENABLE(VIDEO)
60 #include "MediaControlElements.h"
61 #endif
62 #include "InputElement.h"
63 #include "Page.h"
64 #include "RenderListBox.h"
65 #include "RenderMenuList.h"
66 #include "RenderProgress.h"
67 #include "RenderSlider.h"
68 #include "RenderTable.h"
69 #include "RenderTableCell.h"
70 #include "RenderTableRow.h"
71 #include "RenderView.h"
72 #include "ScrollView.h"
73
74 #include <wtf/PassRefPtr.h>
75
76 namespace WebCore {
77
78 using namespace HTMLNames;
79     
80 bool AXObjectCache::gAccessibilityEnabled = false;
81 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
82
83 AXObjectCache::AXObjectCache(const Document* doc)
84     : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
85 {
86     m_document = const_cast<Document*>(doc);
87 }
88
89 AXObjectCache::~AXObjectCache()
90 {
91     HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end();
92     for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) {
93         AccessibilityObject* obj = (*it).second.get();
94         detachWrapper(obj);
95         obj->detach();
96         removeAXID(obj);
97     }
98 }
99
100 AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
101 {
102     // Find the corresponding accessibility object for the HTMLAreaElement. This should be
103     // in the list of children for its corresponding image.
104     if (!areaElement)
105         return 0;
106     
107     HTMLImageElement* imageElement = areaElement->imageElement();
108     if (!imageElement)
109         return 0;
110     
111     AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer());
112     if (!axRenderImage)
113         return 0;
114     
115     AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children();
116     unsigned count = imageChildren.size();
117     for (unsigned k = 0; k < count; ++k) {
118         AccessibilityObject* child = imageChildren[k].get();
119         if (!child->isImageMapLink())
120             continue;
121         
122         if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement)
123             return child;
124     }    
125     
126     return 0;
127 }
128     
129 AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
130 {
131     // get the focused node in the page
132     Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document();
133     Node* focusedNode = focusedDocument->focusedNode();
134     if (!focusedNode)
135         focusedNode = focusedDocument;
136
137     if (focusedNode->hasTagName(areaTag))
138         return focusedImageMapUIElement(static_cast<HTMLAreaElement*>(focusedNode));
139     
140     RenderObject* focusedNodeRenderer = focusedNode->renderer();
141     if (!focusedNodeRenderer)
142         return 0;
143
144     AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer);
145
146     if (obj->shouldFocusActiveDescendant()) {
147         if (AccessibilityObject* descendant = obj->activeDescendant())
148             obj = descendant;
149     }
150
151     // the HTML element, for example, is focusable but has an AX object that is ignored
152     if (obj->accessibilityIsIgnored())
153         obj = obj->parentObjectUnignored();
154
155     return obj;
156 }
157
158 AccessibilityObject* AXObjectCache::get(Widget* widget)
159 {
160     if (!widget)
161         return 0;
162         
163     AXID axID = m_widgetObjectMapping.get(widget);
164     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
165     if (!axID)
166         return 0;
167     
168     return m_objects.get(axID).get();    
169 }
170     
171 AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
172 {
173     if (!renderer)
174         return 0;
175     
176     AXID axID = m_renderObjectMapping.get(renderer);
177     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
178     if (!axID)
179         return 0;
180     
181     return m_objects.get(axID).get();    
182 }
183
184 // FIXME: This probably belongs on Node.
185 // FIXME: This should take a const char*, but one caller passes nullAtom.
186 bool nodeHasRole(Node* node, const String& role)
187 {
188     if (!node || !node->isElementNode())
189         return false;
190
191     return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role);
192 }
193
194 static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer)
195 {
196     // FIXME: How could renderer->node() ever not be an Element?
197     Node* node = renderer->node();
198
199     // If the node is aria role="list" or the aria role is empty and its a
200     // ul/ol/dl type (it shouldn't be a list if aria says otherwise).
201     if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory"))
202                       || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
203         return AccessibilityList::create(renderer);
204
205     // aria tables
206     if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid"))
207         return AccessibilityARIAGrid::create(renderer);
208     if (nodeHasRole(node, "row"))
209         return AccessibilityARIAGridRow::create(renderer);
210     if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
211         return AccessibilityARIAGridCell::create(renderer);
212
213 #if ENABLE(VIDEO)
214     // media controls
215     if (node && node->isMediaControlElement())
216         return AccessibilityMediaControl::create(renderer);
217 #endif
218
219     if (renderer->isBoxModelObject()) {
220         RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer);
221         if (cssBox->isListBox())
222             return AccessibilityListBox::create(toRenderListBox(cssBox));
223         if (cssBox->isMenuList())
224             return AccessibilityMenuList::create(toRenderMenuList(cssBox));
225
226         // standard tables
227         if (cssBox->isTable())
228             return AccessibilityTable::create(toRenderTable(cssBox));
229         if (cssBox->isTableRow())
230             return AccessibilityTableRow::create(toRenderTableRow(cssBox));
231         if (cssBox->isTableCell())
232             return AccessibilityTableCell::create(toRenderTableCell(cssBox));
233
234 #if ENABLE(PROGRESS_TAG)
235         // progress bar
236         if (cssBox->isProgress())
237             return AccessibilityProgressIndicator::create(toRenderProgress(cssBox));
238 #endif
239
240         // input type=range
241         if (cssBox->isSlider())
242             return AccessibilitySlider::create(toRenderSlider(cssBox));
243     }
244
245     return AccessibilityRenderObject::create(renderer);
246 }
247
248 AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
249 {
250     if (!widget)
251         return 0;
252
253     if (AccessibilityObject* obj = get(widget))
254         return obj;
255     
256     RefPtr<AccessibilityObject> newObj = 0;
257     if (widget->isFrameView())
258         newObj = AccessibilityScrollView::create(static_cast<ScrollView*>(widget));
259     else if (widget->isScrollbar())
260         newObj = AccessibilityScrollbar::create(static_cast<Scrollbar*>(widget));
261         
262     getAXID(newObj.get());
263     
264     m_widgetObjectMapping.set(widget, newObj->axObjectID());
265     m_objects.set(newObj->axObjectID(), newObj);    
266     attachWrapper(newObj.get());
267     return newObj.get();
268 }
269     
270 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
271 {
272     if (!renderer)
273         return 0;
274
275     if (AccessibilityObject* obj = get(renderer))
276         return obj;
277
278     RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer);
279
280     getAXID(newObj.get());
281
282     m_renderObjectMapping.set(renderer, newObj->axObjectID());
283     m_objects.set(newObj->axObjectID(), newObj);
284     attachWrapper(newObj.get());
285     return newObj.get();
286 }
287     
288 AccessibilityObject* AXObjectCache::rootObject()
289 {
290     return getOrCreate(m_document->view());
291 }
292
293 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
294 {
295     RefPtr<AccessibilityObject> obj = 0;
296     
297     // will be filled in...
298     switch (role) {
299     case ListBoxOptionRole:
300         obj = AccessibilityListBoxOption::create();
301         break;
302     case ImageMapLinkRole:
303         obj = AccessibilityImageMapLink::create();
304         break;
305     case ColumnRole:
306         obj = AccessibilityTableColumn::create();
307         break;            
308     case TableHeaderContainerRole:
309         obj = AccessibilityTableHeaderContainer::create();
310         break;   
311     case SliderThumbRole:
312         obj = AccessibilitySliderThumb::create();
313         break;
314     case MenuListPopupRole:
315         obj = AccessibilityMenuListPopup::create();
316         break;
317     case MenuListOptionRole:
318         obj = AccessibilityMenuListOption::create();
319         break;
320     default:
321         obj = 0;
322     }
323     
324     if (obj)
325         getAXID(obj.get());
326     else
327         return 0;
328
329     m_objects.set(obj->axObjectID(), obj);    
330     attachWrapper(obj.get());
331     return obj.get();
332 }
333
334 void AXObjectCache::remove(AXID axID)
335 {
336     if (!axID)
337         return;
338     
339     // first fetch object to operate some cleanup functions on it 
340     AccessibilityObject* obj = m_objects.get(axID).get();
341     if (!obj)
342         return;
343     
344     detachWrapper(obj);
345     obj->detach();
346     removeAXID(obj);
347     
348     // finally remove the object
349     if (!m_objects.take(axID))
350         return;
351     
352     ASSERT(m_objects.size() >= m_idsInUse.size());    
353 }
354     
355 void AXObjectCache::remove(RenderObject* renderer)
356 {
357     if (!renderer)
358         return;
359     
360     AXID axID = m_renderObjectMapping.get(renderer);
361     remove(axID);
362     m_renderObjectMapping.remove(renderer);
363 }
364
365 void AXObjectCache::remove(Widget* view)
366 {
367     if (!view)
368         return;
369         
370     AXID axID = m_widgetObjectMapping.get(view);
371     remove(axID);
372     m_widgetObjectMapping.remove(view);
373 }
374     
375     
376 #if !PLATFORM(WIN) || OS(WINCE)
377 AXID AXObjectCache::platformGenerateAXID() const
378 {
379     static AXID lastUsedID = 0;
380
381     // Generate a new ID.
382     AXID objID = lastUsedID;
383     do {
384         ++objID;
385     } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
386
387     lastUsedID = objID;
388
389     return objID;
390 }
391 #endif
392
393 AXID AXObjectCache::getAXID(AccessibilityObject* obj)
394 {
395     // check for already-assigned ID
396     AXID objID = obj->axObjectID();
397     if (objID) {
398         ASSERT(m_idsInUse.contains(objID));
399         return objID;
400     }
401
402     objID = platformGenerateAXID();
403
404     m_idsInUse.add(objID);
405     obj->setAXObjectID(objID);
406     
407     return objID;
408 }
409
410 void AXObjectCache::removeAXID(AccessibilityObject* object)
411 {
412     if (!object)
413         return;
414     
415     AXID objID = object->axObjectID();
416     if (!objID)
417         return;
418     ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
419     ASSERT(m_idsInUse.contains(objID));
420     object->setAXObjectID(0);
421     m_idsInUse.remove(objID);
422 }
423
424 #if HAVE(ACCESSIBILITY)
425 void AXObjectCache::contentChanged(RenderObject* renderer)
426 {
427     AccessibilityObject* object = getOrCreate(renderer);
428     if (object)
429         object->contentChanged(); 
430 }
431 #endif
432
433 void AXObjectCache::childrenChanged(RenderObject* renderer)
434 {
435     if (!renderer)
436         return;
437  
438     AXID axID = m_renderObjectMapping.get(renderer);
439     if (!axID)
440         return;
441     
442     AccessibilityObject* obj = m_objects.get(axID).get();
443     if (obj)
444         obj->childrenChanged();
445 }
446     
447 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
448 {
449     m_notificationPostTimer.stop();
450
451     unsigned i = 0, count = m_notificationsToPost.size();
452     for (i = 0; i < count; ++i) {
453         AccessibilityObject* obj = m_notificationsToPost[i].first.get();
454 #ifndef NDEBUG
455         // Make sure none of the render views are in the process of being layed out.
456         // Notifications should only be sent after the renderer has finished
457         if (obj->isAccessibilityRenderObject()) {
458             AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj);
459             RenderObject* renderer = renderObj->renderer();
460             if (renderer && renderer->view())
461                 ASSERT(!renderer->view()->layoutState());
462         }
463 #endif
464         
465         postPlatformNotification(obj, m_notificationsToPost[i].second);
466     }
467     
468     m_notificationsToPost.clear();
469 }
470     
471 #if HAVE(ACCESSIBILITY)
472 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType)
473 {
474     // Notifications for text input objects are sent to that object.
475     // All others are sent to the top WebArea.
476     if (!renderer)
477         return;
478     
479     // Get an accessibility object that already exists. One should not be created here
480     // because a render update may be in progress and creating an AX object can re-trigger a layout
481     RefPtr<AccessibilityObject> object = get(renderer);
482     while (!object && renderer) {
483         renderer = renderer->parent();
484         object = get(renderer); 
485     }
486     
487     if (!renderer)
488         return;
489     
490     postNotification(object.get(), renderer->document(), notification, postToElement, postType);
491 }
492
493 void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType)
494 {
495     if (object && !postToElement)
496         object = object->observableObject();
497
498     if (!object && document)
499         object = get(document->renderer());
500
501     if (!object)
502         return;
503
504     if (postType == PostAsynchronously) {
505         m_notificationsToPost.append(make_pair(object, notification));
506         if (!m_notificationPostTimer.isActive())
507             m_notificationPostTimer.startOneShot(0);
508     } else
509         postPlatformNotification(object, notification);
510 }
511
512 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
513 {
514     // postToElement is false so that you can pass in any child of an element and it will go up the parent tree
515     // to find the container which should send out the notification.
516     postNotification(renderer, AXSelectedChildrenChanged, false);
517 }
518
519 void AXObjectCache::nodeTextChangeNotification(RenderObject* renderer, AXTextChange textChange, unsigned offset, unsigned count)
520 {
521     if (!renderer)
522         return;
523
524     // Delegate on the right platform
525     AccessibilityObject* obj = getOrCreate(renderer);
526     nodeTextChangePlatformNotification(obj, textChange, offset, count);
527 }
528 #endif
529
530 #if HAVE(ACCESSIBILITY)
531
532 void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
533 {
534     if (!view)
535         return;
536     
537     // We don't want to create a scroll view from this method, only update an existing one.
538     AccessibilityObject* scrollViewObject = get(view);
539     if (scrollViewObject)
540         scrollViewObject->updateChildrenIfNecessary();
541 }
542     
543 void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer)
544 {
545     if (!renderer)
546         return;
547     AccessibilityObject* obj = getOrCreate(renderer);
548     if (obj)
549         obj->handleAriaExpandedChanged();
550 }
551     
552 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer)
553 {
554     if (!renderer)
555         return;
556     AccessibilityObject* obj = getOrCreate(renderer);
557     if (obj)
558         obj->handleActiveDescendantChanged();
559 }
560
561 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer)
562 {
563     if (!renderer)
564         return;
565     AccessibilityObject* obj = getOrCreate(renderer);
566     if (obj && obj->isAccessibilityRenderObject())
567         static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole();
568 }
569 #endif
570
571 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
572 {
573     if (!isNodeInUse(textMarkerData.node))
574         return VisiblePosition();
575     
576     VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity);
577     Position deepPos = visiblePos.deepEquivalent();
578     if (deepPos.isNull())
579         return VisiblePosition();
580     
581     RenderObject* renderer = deepPos.node()->renderer();
582     if (!renderer)
583         return VisiblePosition();
584     
585     AXObjectCache* cache = renderer->document()->axObjectCache();
586     if (!cache->isIDinUse(textMarkerData.axID))
587         return VisiblePosition();
588     
589     if (deepPos.node() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
590         return VisiblePosition();
591     
592     return visiblePos;
593 }
594
595 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
596 {
597     // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
598     // This also allows callers to check for failure by looking at textMarkerData upon return.
599     memset(&textMarkerData, 0, sizeof(TextMarkerData));
600     
601     if (visiblePos.isNull())
602         return;
603     
604     Position deepPos = visiblePos.deepEquivalent();
605     Node* domNode = deepPos.node();
606     ASSERT(domNode);
607     if (!domNode)
608         return;
609     
610     if (domNode->isHTMLElement()) {
611         InputElement* inputElement = toInputElement(static_cast<Element*>(domNode));
612         if (inputElement && inputElement->isPasswordField())
613             return;
614     }
615     
616     // locate the renderer, which must exist for a visible dom node
617     RenderObject* renderer = domNode->renderer();
618     ASSERT(renderer);
619     
620     // find or create an accessibility object for this renderer
621     AXObjectCache* cache = renderer->document()->axObjectCache();
622     RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer);
623     
624     textMarkerData.axID = obj.get()->axObjectID();
625     textMarkerData.node = domNode;
626     textMarkerData.offset = deepPos.deprecatedEditingOffset();
627     textMarkerData.affinity = visiblePos.affinity();   
628     
629     cache->setNodeInUse(domNode);
630 }
631     
632 } // namespace WebCore