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