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