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
AXObjectCache(const Document * doc)83 AXObjectCache::AXObjectCache(const Document* doc)
84 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
85 {
86 m_document = const_cast<Document*>(doc);
87 }
88
~AXObjectCache()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
focusedImageMapUIElement(HTMLAreaElement * areaElement)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
focusedUIElementForPage(const Page * page)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
get(Widget * widget)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
get(RenderObject * renderer)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.
nodeHasRole(Node * node,const String & role)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
createFromRenderer(RenderObject * renderer)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
getOrCreate(Widget * widget)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
getOrCreate(RenderObject * renderer)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
rootObject()288 AccessibilityObject* AXObjectCache::rootObject()
289 {
290 return getOrCreate(m_document->view());
291 }
292
rootObjectForFrame(Frame * frame)293 AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame)
294 {
295 if (!frame)
296 return 0;
297 return getOrCreate(frame->view());
298 }
299
getOrCreate(AccessibilityRole role)300 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
301 {
302 RefPtr<AccessibilityObject> obj = 0;
303
304 // will be filled in...
305 switch (role) {
306 case ListBoxOptionRole:
307 obj = AccessibilityListBoxOption::create();
308 break;
309 case ImageMapLinkRole:
310 obj = AccessibilityImageMapLink::create();
311 break;
312 case ColumnRole:
313 obj = AccessibilityTableColumn::create();
314 break;
315 case TableHeaderContainerRole:
316 obj = AccessibilityTableHeaderContainer::create();
317 break;
318 case SliderThumbRole:
319 obj = AccessibilitySliderThumb::create();
320 break;
321 case MenuListPopupRole:
322 obj = AccessibilityMenuListPopup::create();
323 break;
324 case MenuListOptionRole:
325 obj = AccessibilityMenuListOption::create();
326 break;
327 default:
328 obj = 0;
329 }
330
331 if (obj)
332 getAXID(obj.get());
333 else
334 return 0;
335
336 m_objects.set(obj->axObjectID(), obj);
337 attachWrapper(obj.get());
338 return obj.get();
339 }
340
remove(AXID axID)341 void AXObjectCache::remove(AXID axID)
342 {
343 if (!axID)
344 return;
345
346 // first fetch object to operate some cleanup functions on it
347 AccessibilityObject* obj = m_objects.get(axID).get();
348 if (!obj)
349 return;
350
351 detachWrapper(obj);
352 obj->detach();
353 removeAXID(obj);
354
355 // finally remove the object
356 if (!m_objects.take(axID))
357 return;
358
359 ASSERT(m_objects.size() >= m_idsInUse.size());
360 }
361
remove(RenderObject * renderer)362 void AXObjectCache::remove(RenderObject* renderer)
363 {
364 if (!renderer)
365 return;
366
367 AXID axID = m_renderObjectMapping.get(renderer);
368 remove(axID);
369 m_renderObjectMapping.remove(renderer);
370 }
371
remove(Widget * view)372 void AXObjectCache::remove(Widget* view)
373 {
374 if (!view)
375 return;
376
377 AXID axID = m_widgetObjectMapping.get(view);
378 remove(axID);
379 m_widgetObjectMapping.remove(view);
380 }
381
382
383 #if !PLATFORM(WIN) || OS(WINCE)
platformGenerateAXID() const384 AXID AXObjectCache::platformGenerateAXID() const
385 {
386 static AXID lastUsedID = 0;
387
388 // Generate a new ID.
389 AXID objID = lastUsedID;
390 do {
391 ++objID;
392 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
393
394 lastUsedID = objID;
395
396 return objID;
397 }
398 #endif
399
getAXID(AccessibilityObject * obj)400 AXID AXObjectCache::getAXID(AccessibilityObject* obj)
401 {
402 // check for already-assigned ID
403 AXID objID = obj->axObjectID();
404 if (objID) {
405 ASSERT(m_idsInUse.contains(objID));
406 return objID;
407 }
408
409 objID = platformGenerateAXID();
410
411 m_idsInUse.add(objID);
412 obj->setAXObjectID(objID);
413
414 return objID;
415 }
416
removeAXID(AccessibilityObject * object)417 void AXObjectCache::removeAXID(AccessibilityObject* object)
418 {
419 if (!object)
420 return;
421
422 AXID objID = object->axObjectID();
423 if (!objID)
424 return;
425 ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
426 ASSERT(m_idsInUse.contains(objID));
427 object->setAXObjectID(0);
428 m_idsInUse.remove(objID);
429 }
430
431 #if HAVE(ACCESSIBILITY)
contentChanged(RenderObject * renderer)432 void AXObjectCache::contentChanged(RenderObject* renderer)
433 {
434 AccessibilityObject* object = getOrCreate(renderer);
435 if (object)
436 object->contentChanged();
437 }
438 #endif
439
childrenChanged(RenderObject * renderer)440 void AXObjectCache::childrenChanged(RenderObject* renderer)
441 {
442 if (!renderer)
443 return;
444
445 AXID axID = m_renderObjectMapping.get(renderer);
446 if (!axID)
447 return;
448
449 AccessibilityObject* obj = m_objects.get(axID).get();
450 if (obj)
451 obj->childrenChanged();
452 }
453
notificationPostTimerFired(Timer<AXObjectCache> *)454 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
455 {
456 m_notificationPostTimer.stop();
457
458 unsigned i = 0, count = m_notificationsToPost.size();
459 for (i = 0; i < count; ++i) {
460 AccessibilityObject* obj = m_notificationsToPost[i].first.get();
461 #ifndef NDEBUG
462 // Make sure none of the render views are in the process of being layed out.
463 // Notifications should only be sent after the renderer has finished
464 if (obj->isAccessibilityRenderObject()) {
465 AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj);
466 RenderObject* renderer = renderObj->renderer();
467 if (renderer && renderer->view())
468 ASSERT(!renderer->view()->layoutState());
469 }
470 #endif
471
472 postPlatformNotification(obj, m_notificationsToPost[i].second);
473 }
474
475 m_notificationsToPost.clear();
476 }
477
478 #if HAVE(ACCESSIBILITY)
postNotification(RenderObject * renderer,AXNotification notification,bool postToElement,PostType postType)479 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType)
480 {
481 // Notifications for text input objects are sent to that object.
482 // All others are sent to the top WebArea.
483 if (!renderer)
484 return;
485
486 // Get an accessibility object that already exists. One should not be created here
487 // because a render update may be in progress and creating an AX object can re-trigger a layout
488 RefPtr<AccessibilityObject> object = get(renderer);
489 while (!object && renderer) {
490 renderer = renderer->parent();
491 object = get(renderer);
492 }
493
494 if (!renderer)
495 return;
496
497 postNotification(object.get(), renderer->document(), notification, postToElement, postType);
498 }
499
postNotification(AccessibilityObject * object,Document * document,AXNotification notification,bool postToElement,PostType postType)500 void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType)
501 {
502 if (object && !postToElement)
503 object = object->observableObject();
504
505 if (!object && document)
506 object = get(document->renderer());
507
508 if (!object)
509 return;
510
511 if (postType == PostAsynchronously) {
512 m_notificationsToPost.append(make_pair(object, notification));
513 if (!m_notificationPostTimer.isActive())
514 m_notificationPostTimer.startOneShot(0);
515 } else
516 postPlatformNotification(object, notification);
517 }
518
selectedChildrenChanged(RenderObject * renderer)519 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
520 {
521 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree
522 // to find the container which should send out the notification.
523 postNotification(renderer, AXSelectedChildrenChanged, false);
524 }
525
nodeTextChangeNotification(RenderObject * renderer,AXTextChange textChange,unsigned offset,unsigned count)526 void AXObjectCache::nodeTextChangeNotification(RenderObject* renderer, AXTextChange textChange, unsigned offset, unsigned count)
527 {
528 if (!renderer)
529 return;
530
531 // Delegate on the right platform
532 AccessibilityObject* obj = getOrCreate(renderer);
533 nodeTextChangePlatformNotification(obj, textChange, offset, count);
534 }
535 #endif
536
537 #if HAVE(ACCESSIBILITY)
538
handleScrollbarUpdate(ScrollView * view)539 void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
540 {
541 if (!view)
542 return;
543
544 // We don't want to create a scroll view from this method, only update an existing one.
545 AccessibilityObject* scrollViewObject = get(view);
546 if (scrollViewObject)
547 scrollViewObject->updateChildrenIfNecessary();
548 }
549
handleAriaExpandedChange(RenderObject * renderer)550 void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer)
551 {
552 if (!renderer)
553 return;
554 AccessibilityObject* obj = getOrCreate(renderer);
555 if (obj)
556 obj->handleAriaExpandedChanged();
557 }
558
handleActiveDescendantChanged(RenderObject * renderer)559 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer)
560 {
561 if (!renderer)
562 return;
563 AccessibilityObject* obj = getOrCreate(renderer);
564 if (obj)
565 obj->handleActiveDescendantChanged();
566 }
567
handleAriaRoleChanged(RenderObject * renderer)568 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer)
569 {
570 if (!renderer)
571 return;
572 AccessibilityObject* obj = getOrCreate(renderer);
573 if (obj && obj->isAccessibilityRenderObject())
574 static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole();
575 }
576 #endif
577
visiblePositionForTextMarkerData(TextMarkerData & textMarkerData)578 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
579 {
580 if (!isNodeInUse(textMarkerData.node))
581 return VisiblePosition();
582
583 // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects.
584 VisiblePosition visiblePos = VisiblePosition(Position(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity);
585 Position deepPos = visiblePos.deepEquivalent();
586 if (deepPos.isNull())
587 return VisiblePosition();
588
589 RenderObject* renderer = deepPos.deprecatedNode()->renderer();
590 if (!renderer)
591 return VisiblePosition();
592
593 AXObjectCache* cache = renderer->document()->axObjectCache();
594 if (!cache->isIDinUse(textMarkerData.axID))
595 return VisiblePosition();
596
597 if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
598 return VisiblePosition();
599
600 return visiblePos;
601 }
602
textMarkerDataForVisiblePosition(TextMarkerData & textMarkerData,const VisiblePosition & visiblePos)603 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
604 {
605 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
606 // This also allows callers to check for failure by looking at textMarkerData upon return.
607 memset(&textMarkerData, 0, sizeof(TextMarkerData));
608
609 if (visiblePos.isNull())
610 return;
611
612 Position deepPos = visiblePos.deepEquivalent();
613 Node* domNode = deepPos.deprecatedNode();
614 ASSERT(domNode);
615 if (!domNode)
616 return;
617
618 if (domNode->isHTMLElement()) {
619 InputElement* inputElement = domNode->toInputElement();
620 if (inputElement && inputElement->isPasswordField())
621 return;
622 }
623
624 // locate the renderer, which must exist for a visible dom node
625 RenderObject* renderer = domNode->renderer();
626 ASSERT(renderer);
627
628 // find or create an accessibility object for this renderer
629 AXObjectCache* cache = renderer->document()->axObjectCache();
630 RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer);
631
632 textMarkerData.axID = obj.get()->axObjectID();
633 textMarkerData.node = domNode;
634 textMarkerData.offset = deepPos.deprecatedEditingOffset();
635 textMarkerData.affinity = visiblePos.affinity();
636
637 cache->setNodeInUse(domNode);
638 }
639
640 } // namespace WebCore
641