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