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