Support ARIA "tab" roles
[WebKit-https.git] / WebCore / accessibility / AXObjectCache.cpp
1 /*
2  * Copyright (C) 2008, 2009 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 "AccessibilityARIAGridRow.h"
34 #include "AccessibilityARIAGridCell.h"
35 #include "AccessibilityList.h"
36 #include "AccessibilityListBox.h"
37 #include "AccessibilityListBoxOption.h"
38 #include "AccessibilityImageMapLink.h"
39 #include "AccessibilityMediaControls.h"
40 #include "AccessibilityRenderObject.h"
41 #include "AccessibilitySlider.h"
42 #include "AccessibilityTable.h"
43 #include "AccessibilityTableCell.h"
44 #include "AccessibilityTableColumn.h"
45 #include "AccessibilityTableHeaderContainer.h"
46 #include "AccessibilityTableRow.h"
47 #include "FocusController.h"
48 #include "Frame.h"
49 #include "HTMLNames.h"
50 #if ENABLE(VIDEO)
51 #include "MediaControlElements.h"
52 #endif
53 #include "InputElement.h"
54 #include "Page.h"
55 #include "RenderObject.h"
56 #include "RenderView.h"
57
58 #include <wtf/PassRefPtr.h>
59
60 namespace WebCore {
61
62 using namespace HTMLNames;
63     
64 bool AXObjectCache::gAccessibilityEnabled = false;
65 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
66
67 AXObjectCache::AXObjectCache()
68     : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
69 {
70 }
71
72 AXObjectCache::~AXObjectCache()
73 {
74     HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end();
75     for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) {
76         AccessibilityObject* obj = (*it).second.get();
77         detachWrapper(obj);
78         obj->detach();
79         removeAXID(obj);
80     }
81 }
82
83 AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
84 {
85     // get the focused node in the page
86     Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document();
87     Node* focusedNode = focusedDocument->focusedNode();
88     if (!focusedNode)
89         focusedNode = focusedDocument;
90
91     RenderObject* focusedNodeRenderer = focusedNode->renderer();
92     if (!focusedNodeRenderer)
93         return 0;
94
95     AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer);
96
97     if (obj->shouldFocusActiveDescendant()) {
98         if (AccessibilityObject* descendant = obj->activeDescendant())
99             obj = descendant;
100     }
101
102     // the HTML element, for example, is focusable but has an AX object that is ignored
103     if (obj->accessibilityIsIgnored())
104         obj = obj->parentObjectUnignored();
105
106     return obj;
107 }
108
109 AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
110 {
111     if (!renderer)
112         return 0;
113     
114     AccessibilityObject* obj = 0;
115     AXID axID = m_renderObjectMapping.get(renderer);
116     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
117
118     if (axID)
119         obj = m_objects.get(axID).get();
120     
121     return obj;
122 }
123     
124 bool AXObjectCache::nodeIsAriaType(Node* node, String role)
125 {
126     if (!node || !node->isElementNode())
127         return false;
128     
129     return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role);
130 }
131
132 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
133 {
134     if (!renderer)
135         return 0;
136     
137     AccessibilityObject* obj = get(renderer);
138
139     if (!obj) {
140         Node* node = renderer->node();
141         RefPtr<AccessibilityObject> newObj = 0;
142         if (renderer->isListBox())
143             newObj = AccessibilityListBox::create(renderer);
144
145         // 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). 
146         else if (node && (nodeIsAriaType(node, "list") 
147                           || (nodeIsAriaType(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
148             newObj = AccessibilityList::create(renderer);
149         
150         // aria tables
151         else if (nodeIsAriaType(node, "grid"))
152             newObj = AccessibilityARIAGrid::create(renderer);
153         else if (nodeIsAriaType(node, "row"))
154             newObj = AccessibilityARIAGridRow::create(renderer);
155         else if (nodeIsAriaType(node, "gridcell") || nodeIsAriaType(node, "columnheader") || nodeIsAriaType(node, "rowheader"))
156             newObj = AccessibilityARIAGridCell::create(renderer);
157
158         // standard tables
159         else if (renderer->isTable())
160             newObj = AccessibilityTable::create(renderer);
161         else if (renderer->isTableRow())
162             newObj = AccessibilityTableRow::create(renderer);
163         else if (renderer->isTableCell())
164             newObj = AccessibilityTableCell::create(renderer);
165
166 #if ENABLE(VIDEO)
167         // media controls
168         else if (renderer->node() && renderer->node()->isMediaControlElement())
169             newObj = AccessibilityMediaControl::create(renderer);
170 #endif
171
172         // input type=range
173         else if (renderer->isSlider())
174             newObj = AccessibilitySlider::create(renderer);
175
176         else
177             newObj = AccessibilityRenderObject::create(renderer);
178         
179         obj = newObj.get();
180         
181         getAXID(obj);
182         
183         m_renderObjectMapping.set(renderer, obj->axObjectID());
184         m_objects.set(obj->axObjectID(), obj);    
185         attachWrapper(obj);
186     }
187     
188     return obj;
189 }
190
191 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
192 {
193     RefPtr<AccessibilityObject> obj = 0;
194     
195     // will be filled in...
196     switch (role) {
197         case ListBoxOptionRole:
198             obj = AccessibilityListBoxOption::create();
199             break;
200         case ImageMapLinkRole:
201             obj = AccessibilityImageMapLink::create();
202             break;
203         case ColumnRole:
204             obj = AccessibilityTableColumn::create();
205             break;            
206         case TableHeaderContainerRole:
207             obj = AccessibilityTableHeaderContainer::create();
208             break;   
209         case SliderThumbRole:
210             obj = AccessibilitySliderThumb::create();
211             break;
212         default:
213             obj = 0;
214     }
215     
216     if (obj)
217         getAXID(obj.get());
218     else
219         return 0;
220
221     m_objects.set(obj->axObjectID(), obj);    
222     attachWrapper(obj.get());
223     return obj.get();
224 }
225
226 void AXObjectCache::remove(AXID axID)
227 {
228     if (!axID)
229         return;
230     
231     // first fetch object to operate some cleanup functions on it 
232     AccessibilityObject* obj = m_objects.get(axID).get();
233     if (!obj)
234         return;
235     
236     detachWrapper(obj);
237     obj->detach();
238     removeAXID(obj);
239     
240     // finally remove the object
241     if (!m_objects.take(axID)) {
242         return;
243     }
244     
245     ASSERT(m_objects.size() >= m_idsInUse.size());    
246 }
247     
248 void AXObjectCache::remove(RenderObject* renderer)
249 {
250     if (!renderer)
251         return;
252     
253     AXID axID = m_renderObjectMapping.get(renderer);
254     remove(axID);
255     m_renderObjectMapping.remove(renderer);
256 }
257
258 #if !PLATFORM(WIN)
259 AXID AXObjectCache::platformGenerateAXID() const
260 {
261     static AXID lastUsedID = 0;
262
263     // Generate a new ID.
264     AXID objID = lastUsedID;
265     do {
266         ++objID;
267     } while (objID == 0 || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
268
269     lastUsedID = objID;
270
271     return objID;
272 }
273 #endif
274
275 AXID AXObjectCache::getAXID(AccessibilityObject* obj)
276 {
277     // check for already-assigned ID
278     AXID objID = obj->axObjectID();
279     if (objID) {
280         ASSERT(m_idsInUse.contains(objID));
281         return objID;
282     }
283
284     objID = platformGenerateAXID();
285
286     m_idsInUse.add(objID);
287     obj->setAXObjectID(objID);
288     
289     return objID;
290 }
291
292 void AXObjectCache::removeAXID(AccessibilityObject* obj)
293 {
294     if (!obj)
295         return;
296     
297     AXID objID = obj->axObjectID();
298     if (objID == 0)
299         return;
300     ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
301     ASSERT(m_idsInUse.contains(objID));
302     obj->setAXObjectID(0);
303     m_idsInUse.remove(objID);
304 }
305
306 void AXObjectCache::childrenChanged(RenderObject* renderer)
307 {
308     if (!renderer)
309         return;
310  
311     AXID axID = m_renderObjectMapping.get(renderer);
312     if (!axID)
313         return;
314     
315     AccessibilityObject* obj = m_objects.get(axID).get();
316     if (obj)
317         obj->childrenChanged();
318 }
319     
320 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
321 {
322     m_notificationPostTimer.stop();
323
324     unsigned i = 0, count = m_notificationsToPost.size();
325     for (i = 0; i < count; ++i) {
326         AccessibilityObject* obj = m_notificationsToPost[i].first.get();
327 #ifndef NDEBUG
328         // Make sure none of the render views are in the process of being layed out.
329         // Notifications should only be sent after the renderer has finished
330         if (obj->isAccessibilityRenderObject()) {
331             AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj);
332             RenderObject* renderer = renderObj->renderer();
333             if (renderer && renderer->view())
334                 ASSERT(!renderer->view()->layoutState());
335         }
336 #endif
337         
338         postPlatformNotification(obj, m_notificationsToPost[i].second);
339     }
340     
341     m_notificationsToPost.clear();
342 }
343     
344 #if HAVE(ACCESSIBILITY)
345 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement)
346 {
347     // Notifications for text input objects are sent to that object.
348     // All others are sent to the top WebArea.
349     if (!renderer)
350         return;
351     
352     // Get an accessibility object that already exists. One should not be created here
353     // because a render update may be in progress and creating an AX object can re-trigger a layout
354     RefPtr<AccessibilityObject> obj = get(renderer);
355     while (!obj && renderer) {
356         renderer = renderer->parent();
357         obj = get(renderer); 
358     }
359     
360     if (!renderer)
361         return;
362
363     if (obj && !postToElement)
364         obj = obj->observableObject();
365     
366     Document* document = renderer->document();
367     if (!obj && document)
368         obj = get(document->renderer());
369     
370     if (!obj)
371         return;
372
373     m_notificationsToPost.append(make_pair(obj, notification));
374     if (!m_notificationPostTimer.isActive())
375         m_notificationPostTimer.startOneShot(0);
376 }
377
378 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
379 {
380     postNotification(renderer, AXSelectedChildrenChanged, true);
381 }
382 #endif
383
384 #if HAVE(ACCESSIBILITY)
385 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer)
386 {
387     if (!renderer)
388         return;
389     AccessibilityObject* obj = getOrCreate(renderer);
390     if (obj)
391         obj->handleActiveDescendantChanged();
392 }
393
394 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer)
395 {
396     if (!renderer)
397         return;
398     AccessibilityObject* obj = getOrCreate(renderer);
399     if (obj && obj->isAccessibilityRenderObject())
400         static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole();
401 }
402 #endif
403     
404 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
405 {
406     VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity);
407     Position deepPos = visiblePos.deepEquivalent();
408     if (deepPos.isNull())
409         return VisiblePosition();
410     
411     RenderObject* renderer = deepPos.node()->renderer();
412     if (!renderer)
413         return VisiblePosition();
414     
415     AXObjectCache* cache = renderer->document()->axObjectCache();
416     if (!cache->isIDinUse(textMarkerData.axID))
417         return VisiblePosition();
418     
419     if (deepPos.node() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
420         return VisiblePosition();
421     
422     return visiblePos;
423 }
424
425 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
426 {
427     // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
428     // This also allows callers to check for failure by looking at textMarkerData upon return.
429     memset(&textMarkerData, 0, sizeof(TextMarkerData));
430     
431     if (visiblePos.isNull())
432         return;
433     
434     Position deepPos = visiblePos.deepEquivalent();
435     Node* domNode = deepPos.node();
436     ASSERT(domNode);
437     if (!domNode)
438         return;
439     
440     if (domNode->isHTMLElement()) {
441         InputElement* inputElement = toInputElement(static_cast<Element*>(domNode));
442         if (inputElement && inputElement->isPasswordField())
443             return;
444     }
445     
446     // locate the renderer, which must exist for a visible dom node
447     RenderObject* renderer = domNode->renderer();
448     ASSERT(renderer);
449     
450     // find or create an accessibility object for this renderer
451     AXObjectCache* cache = renderer->document()->axObjectCache();
452     RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer);
453     
454     textMarkerData.axID = obj.get()->axObjectID();
455     textMarkerData.node = domNode;
456     textMarkerData.offset = deepPos.deprecatedEditingOffset();
457     textMarkerData.affinity = visiblePos.affinity();    
458 }
459     
460 } // namespace WebCore