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
31 #include "core/accessibility/AXObjectCache.h"
32
33 #include "HTMLNames.h"
34 #include "core/accessibility/AXARIAGrid.h"
35 #include "core/accessibility/AXARIAGridCell.h"
36 #include "core/accessibility/AXARIAGridRow.h"
37 #include "core/accessibility/AXImageMapLink.h"
38 #include "core/accessibility/AXInlineTextBox.h"
39 #include "core/accessibility/AXList.h"
40 #include "core/accessibility/AXListBox.h"
41 #include "core/accessibility/AXListBoxOption.h"
42 #include "core/accessibility/AXMediaControls.h"
43 #include "core/accessibility/AXMenuList.h"
44 #include "core/accessibility/AXMenuListOption.h"
45 #include "core/accessibility/AXMenuListPopup.h"
46 #include "core/accessibility/AXProgressIndicator.h"
47 #include "core/accessibility/AXRenderObject.h"
48 #include "core/accessibility/AXSVGRoot.h"
49 #include "core/accessibility/AXScrollView.h"
50 #include "core/accessibility/AXScrollbar.h"
51 #include "core/accessibility/AXSlider.h"
52 #include "core/accessibility/AXSpinButton.h"
53 #include "core/accessibility/AXTable.h"
54 #include "core/accessibility/AXTableCell.h"
55 #include "core/accessibility/AXTableColumn.h"
56 #include "core/accessibility/AXTableHeaderContainer.h"
57 #include "core/accessibility/AXTableRow.h"
58 #include "core/dom/Document.h"
59 #include "core/frame/Frame.h"
60 #include "core/html/HTMLAreaElement.h"
61 #include "core/html/HTMLImageElement.h"
62 #include "core/html/HTMLInputElement.h"
63 #include "core/html/HTMLLabelElement.h"
64 #include "core/page/Chrome.h"
65 #include "core/page/ChromeClient.h"
66 #include "core/page/FocusController.h"
67 #include "core/page/Page.h"
68 #include "core/rendering/AbstractInlineTextBox.h"
69 #include "core/rendering/RenderListBox.h"
70 #include "core/rendering/RenderMenuList.h"
71 #include "core/rendering/RenderProgress.h"
72 #include "core/rendering/RenderSlider.h"
73 #include "core/rendering/RenderTable.h"
74 #include "core/rendering/RenderTableCell.h"
75 #include "core/rendering/RenderTableRow.h"
76 #include "core/rendering/RenderView.h"
77 #include "platform/scroll/ScrollView.h"
78 #include "wtf/PassRefPtr.h"
79
80 namespace WebCore {
81
82 using namespace HTMLNames;
83
getIgnored(AXID id) const84 AXObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const
85 {
86 HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id);
87 return it != m_idMapping.end() ? it->value.ignored : DefaultBehavior;
88 }
89
setIgnored(AXID id,AXObjectInclusion inclusion)90 void AXComputedObjectAttributeCache::setIgnored(AXID id, AXObjectInclusion inclusion)
91 {
92 HashMap<AXID, CachedAXObjectAttributes>::iterator it = m_idMapping.find(id);
93 if (it != m_idMapping.end()) {
94 it->value.ignored = inclusion;
95 } else {
96 CachedAXObjectAttributes attributes;
97 attributes.ignored = inclusion;
98 m_idMapping.set(id, attributes);
99 }
100 }
101
clear()102 void AXComputedObjectAttributeCache::clear()
103 {
104 m_idMapping.clear();
105 }
106
107 bool AXObjectCache::gAccessibilityEnabled = false;
108 bool AXObjectCache::gInlineTextBoxAccessibility = false;
109
AXObjectCache(const Document * doc)110 AXObjectCache::AXObjectCache(const Document* doc)
111 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
112 {
113 m_document = const_cast<Document*>(doc);
114 m_computedObjectAttributeCache = AXComputedObjectAttributeCache::create();
115 }
116
~AXObjectCache()117 AXObjectCache::~AXObjectCache()
118 {
119 m_notificationPostTimer.stop();
120
121 HashMap<AXID, RefPtr<AXObject> >::iterator end = m_objects.end();
122 for (HashMap<AXID, RefPtr<AXObject> >::iterator it = m_objects.begin(); it != end; ++it) {
123 AXObject* obj = (*it).value.get();
124 detachWrapper(obj);
125 obj->detach();
126 removeAXID(obj);
127 }
128 }
129
focusedImageMapUIElement(HTMLAreaElement * areaElement)130 AXObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
131 {
132 // Find the corresponding accessibility object for the HTMLAreaElement. This should be
133 // in the list of children for its corresponding image.
134 if (!areaElement)
135 return 0;
136
137 HTMLImageElement* imageElement = areaElement->imageElement();
138 if (!imageElement)
139 return 0;
140
141 AXObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement);
142 if (!axRenderImage)
143 return 0;
144
145 AXObject::AccessibilityChildrenVector imageChildren = axRenderImage->children();
146 unsigned count = imageChildren.size();
147 for (unsigned k = 0; k < count; ++k) {
148 AXObject* child = imageChildren[k].get();
149 if (!child->isImageMapLink())
150 continue;
151
152 if (toAXImageMapLink(child)->areaElement() == areaElement)
153 return child;
154 }
155
156 return 0;
157 }
158
focusedUIElementForPage(const Page * page)159 AXObject* AXObjectCache::focusedUIElementForPage(const Page* page)
160 {
161 if (!gAccessibilityEnabled)
162 return 0;
163
164 // get the focused node in the page
165 Document* focusedDocument = page->focusController().focusedOrMainFrame()->document();
166 Node* focusedNode = focusedDocument->focusedElement();
167 if (!focusedNode)
168 focusedNode = focusedDocument;
169
170 if (isHTMLAreaElement(focusedNode))
171 return focusedImageMapUIElement(toHTMLAreaElement(focusedNode));
172
173 AXObject* obj = focusedNode->document().axObjectCache()->getOrCreate(focusedNode);
174 if (!obj)
175 return 0;
176
177 if (obj->shouldFocusActiveDescendant()) {
178 if (AXObject* descendant = obj->activeDescendant())
179 obj = descendant;
180 }
181
182 // the HTML element, for example, is focusable but has an AX object that is ignored
183 if (obj->accessibilityIsIgnored())
184 obj = obj->parentObjectUnignored();
185
186 return obj;
187 }
188
get(Widget * widget)189 AXObject* AXObjectCache::get(Widget* widget)
190 {
191 if (!widget)
192 return 0;
193
194 AXID axID = m_widgetObjectMapping.get(widget);
195 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
196 if (!axID)
197 return 0;
198
199 return m_objects.get(axID);
200 }
201
get(RenderObject * renderer)202 AXObject* AXObjectCache::get(RenderObject* renderer)
203 {
204 if (!renderer)
205 return 0;
206
207 AXID axID = m_renderObjectMapping.get(renderer);
208 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
209 if (!axID)
210 return 0;
211
212 return m_objects.get(axID);
213 }
214
get(Node * node)215 AXObject* AXObjectCache::get(Node* node)
216 {
217 if (!node)
218 return 0;
219
220 AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0;
221 ASSERT(!HashTraits<AXID>::isDeletedValue(renderID));
222
223 AXID nodeID = m_nodeObjectMapping.get(node);
224 ASSERT(!HashTraits<AXID>::isDeletedValue(nodeID));
225
226 if (node->renderer() && nodeID && !renderID) {
227 // This can happen if an AXNodeObject is created for a node that's not
228 // rendered, but later something changes and it gets a renderer (like if it's
229 // reparented).
230 remove(nodeID);
231 return 0;
232 }
233
234 if (renderID)
235 return m_objects.get(renderID);
236
237 if (!nodeID)
238 return 0;
239
240 return m_objects.get(nodeID);
241 }
242
get(AbstractInlineTextBox * inlineTextBox)243 AXObject* AXObjectCache::get(AbstractInlineTextBox* inlineTextBox)
244 {
245 if (!inlineTextBox)
246 return 0;
247
248 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox);
249 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
250 if (!axID)
251 return 0;
252
253 return m_objects.get(axID);
254 }
255
256 // FIXME: This probably belongs on Node.
257 // FIXME: This should take a const char*, but one caller passes nullAtom.
nodeHasRole(Node * node,const String & role)258 bool nodeHasRole(Node* node, const String& role)
259 {
260 if (!node || !node->isElementNode())
261 return false;
262
263 return equalIgnoringCase(toElement(node)->getAttribute(roleAttr), role);
264 }
265
createFromRenderer(RenderObject * renderer)266 static PassRefPtr<AXObject> createFromRenderer(RenderObject* renderer)
267 {
268 // FIXME: How could renderer->node() ever not be an Element?
269 Node* node = renderer->node();
270
271 // If the node is aria role="list" or the aria role is empty and its a
272 // ul/ol/dl type (it shouldn't be a list if aria says otherwise).
273 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory"))
274 || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
275 return AXList::create(renderer);
276
277 // aria tables
278 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid"))
279 return AXARIAGrid::create(renderer);
280 if (nodeHasRole(node, "row"))
281 return AXARIAGridRow::create(renderer);
282 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
283 return AXARIAGridCell::create(renderer);
284
285 // media controls
286 if (node && node->isMediaControlElement())
287 return AccessibilityMediaControl::create(renderer);
288
289 if (renderer->isSVGRoot())
290 return AXSVGRoot::create(renderer);
291
292 if (renderer->isBoxModelObject()) {
293 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer);
294 if (cssBox->isListBox())
295 return AXListBox::create(toRenderListBox(cssBox));
296 if (cssBox->isMenuList())
297 return AXMenuList::create(toRenderMenuList(cssBox));
298
299 // standard tables
300 if (cssBox->isTable())
301 return AXTable::create(toRenderTable(cssBox));
302 if (cssBox->isTableRow())
303 return AXTableRow::create(toRenderTableRow(cssBox));
304 if (cssBox->isTableCell())
305 return AXTableCell::create(toRenderTableCell(cssBox));
306
307 // progress bar
308 if (cssBox->isProgress())
309 return AXProgressIndicator::create(toRenderProgress(cssBox));
310
311 // input type=range
312 if (cssBox->isSlider())
313 return AXSlider::create(toRenderSlider(cssBox));
314 }
315
316 return AXRenderObject::create(renderer);
317 }
318
createFromNode(Node * node)319 static PassRefPtr<AXObject> createFromNode(Node* node)
320 {
321 return AXNodeObject::create(node);
322 }
323
createFromInlineTextBox(AbstractInlineTextBox * inlineTextBox)324 static PassRefPtr<AXObject> createFromInlineTextBox(AbstractInlineTextBox* inlineTextBox)
325 {
326 return AXInlineTextBox::create(inlineTextBox);
327 }
328
getOrCreate(Widget * widget)329 AXObject* AXObjectCache::getOrCreate(Widget* widget)
330 {
331 if (!widget)
332 return 0;
333
334 if (AXObject* obj = get(widget))
335 return obj;
336
337 RefPtr<AXObject> newObj = 0;
338 if (widget->isFrameView())
339 newObj = AXScrollView::create(toScrollView(widget));
340 else if (widget->isScrollbar())
341 newObj = AXScrollbar::create(toScrollbar(widget));
342
343 // Will crash later if we have two objects for the same widget.
344 ASSERT(!get(widget));
345
346 getAXID(newObj.get());
347
348 m_widgetObjectMapping.set(widget, newObj->axObjectID());
349 m_objects.set(newObj->axObjectID(), newObj);
350 newObj->init();
351 attachWrapper(newObj.get());
352 return newObj.get();
353 }
354
getOrCreate(Node * node)355 AXObject* AXObjectCache::getOrCreate(Node* node)
356 {
357 if (!node)
358 return 0;
359
360 if (AXObject* obj = get(node))
361 return obj;
362
363 if (node->renderer())
364 return getOrCreate(node->renderer());
365
366 if (!node->parentElement())
367 return 0;
368
369 // It's only allowed to create an AXObject from a Node if it's in a canvas subtree.
370 // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes.
371 bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree();
372 bool isHidden = !node->renderer() && isNodeAriaVisible(node);
373 if (!inCanvasSubtree && !isHidden)
374 return 0;
375
376 RefPtr<AXObject> newObj = createFromNode(node);
377
378 // Will crash later if we have two objects for the same node.
379 ASSERT(!get(node));
380
381 getAXID(newObj.get());
382
383 m_nodeObjectMapping.set(node, newObj->axObjectID());
384 m_objects.set(newObj->axObjectID(), newObj);
385 newObj->init();
386 attachWrapper(newObj.get());
387 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
388
389 return newObj.get();
390 }
391
getOrCreate(RenderObject * renderer)392 AXObject* AXObjectCache::getOrCreate(RenderObject* renderer)
393 {
394 if (!renderer)
395 return 0;
396
397 if (AXObject* obj = get(renderer))
398 return obj;
399
400 RefPtr<AXObject> newObj = createFromRenderer(renderer);
401
402 // Will crash later if we have two objects for the same renderer.
403 ASSERT(!get(renderer));
404
405 getAXID(newObj.get());
406
407 m_renderObjectMapping.set(renderer, newObj->axObjectID());
408 m_objects.set(newObj->axObjectID(), newObj);
409 newObj->init();
410 attachWrapper(newObj.get());
411 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
412
413 return newObj.get();
414 }
415
getOrCreate(AbstractInlineTextBox * inlineTextBox)416 AXObject* AXObjectCache::getOrCreate(AbstractInlineTextBox* inlineTextBox)
417 {
418 if (!inlineTextBox)
419 return 0;
420
421 if (AXObject* obj = get(inlineTextBox))
422 return obj;
423
424 RefPtr<AXObject> newObj = createFromInlineTextBox(inlineTextBox);
425
426 // Will crash later if we have two objects for the same inlineTextBox.
427 ASSERT(!get(inlineTextBox));
428
429 getAXID(newObj.get());
430
431 m_inlineTextBoxObjectMapping.set(inlineTextBox, newObj->axObjectID());
432 m_objects.set(newObj->axObjectID(), newObj);
433 newObj->init();
434 attachWrapper(newObj.get());
435 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
436
437 return newObj.get();
438 }
439
rootObject()440 AXObject* AXObjectCache::rootObject()
441 {
442 if (!gAccessibilityEnabled)
443 return 0;
444
445 return getOrCreate(m_document->view());
446 }
447
getOrCreate(AccessibilityRole role)448 AXObject* AXObjectCache::getOrCreate(AccessibilityRole role)
449 {
450 RefPtr<AXObject> obj = 0;
451
452 // will be filled in...
453 switch (role) {
454 case ListBoxOptionRole:
455 obj = AXListBoxOption::create();
456 break;
457 case ImageMapLinkRole:
458 obj = AXImageMapLink::create();
459 break;
460 case ColumnRole:
461 obj = AXTableColumn::create();
462 break;
463 case TableHeaderContainerRole:
464 obj = AXTableHeaderContainer::create();
465 break;
466 case SliderThumbRole:
467 obj = AXSliderThumb::create();
468 break;
469 case MenuListPopupRole:
470 obj = AXMenuListPopup::create();
471 break;
472 case MenuListOptionRole:
473 obj = AXMenuListOption::create();
474 break;
475 case SpinButtonRole:
476 obj = AXSpinButton::create();
477 break;
478 case SpinButtonPartRole:
479 obj = AXSpinButtonPart::create();
480 break;
481 default:
482 obj = 0;
483 }
484
485 if (obj)
486 getAXID(obj.get());
487 else
488 return 0;
489
490 m_objects.set(obj->axObjectID(), obj);
491 obj->init();
492 attachWrapper(obj.get());
493 return obj.get();
494 }
495
remove(AXID axID)496 void AXObjectCache::remove(AXID axID)
497 {
498 if (!axID)
499 return;
500
501 // first fetch object to operate some cleanup functions on it
502 AXObject* obj = m_objects.get(axID);
503 if (!obj)
504 return;
505
506 detachWrapper(obj);
507 obj->detach();
508 removeAXID(obj);
509
510 // finally remove the object
511 if (!m_objects.take(axID))
512 return;
513
514 ASSERT(m_objects.size() >= m_idsInUse.size());
515 }
516
remove(RenderObject * renderer)517 void AXObjectCache::remove(RenderObject* renderer)
518 {
519 if (!renderer)
520 return;
521
522 AXID axID = m_renderObjectMapping.get(renderer);
523 remove(axID);
524 m_renderObjectMapping.remove(renderer);
525 }
526
remove(Node * node)527 void AXObjectCache::remove(Node* node)
528 {
529 if (!node)
530 return;
531
532 removeNodeForUse(node);
533
534 // This is all safe even if we didn't have a mapping.
535 AXID axID = m_nodeObjectMapping.get(node);
536 remove(axID);
537 m_nodeObjectMapping.remove(node);
538
539 if (node->renderer()) {
540 remove(node->renderer());
541 return;
542 }
543 }
544
remove(Widget * view)545 void AXObjectCache::remove(Widget* view)
546 {
547 if (!view)
548 return;
549
550 AXID axID = m_widgetObjectMapping.get(view);
551 remove(axID);
552 m_widgetObjectMapping.remove(view);
553 }
554
remove(AbstractInlineTextBox * inlineTextBox)555 void AXObjectCache::remove(AbstractInlineTextBox* inlineTextBox)
556 {
557 if (!inlineTextBox)
558 return;
559
560 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox);
561 remove(axID);
562 m_inlineTextBoxObjectMapping.remove(inlineTextBox);
563 }
564
platformGenerateAXID() const565 AXID AXObjectCache::platformGenerateAXID() const
566 {
567 static AXID lastUsedID = 0;
568
569 // Generate a new ID.
570 AXID objID = lastUsedID;
571 do {
572 ++objID;
573 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
574
575 lastUsedID = objID;
576
577 return objID;
578 }
579
getAXID(AXObject * obj)580 AXID AXObjectCache::getAXID(AXObject* obj)
581 {
582 // check for already-assigned ID
583 AXID objID = obj->axObjectID();
584 if (objID) {
585 ASSERT(m_idsInUse.contains(objID));
586 return objID;
587 }
588
589 objID = platformGenerateAXID();
590
591 m_idsInUse.add(objID);
592 obj->setAXObjectID(objID);
593
594 return objID;
595 }
596
removeAXID(AXObject * object)597 void AXObjectCache::removeAXID(AXObject* object)
598 {
599 if (!object)
600 return;
601
602 AXID objID = object->axObjectID();
603 if (!objID)
604 return;
605 ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
606 ASSERT(m_idsInUse.contains(objID));
607 object->setAXObjectID(0);
608 m_idsInUse.remove(objID);
609 }
610
selectionChanged(Node * node)611 void AXObjectCache::selectionChanged(Node* node)
612 {
613 // Find the nearest ancestor that already has an accessibility object, since we
614 // might be in the middle of a layout.
615 while (node) {
616 if (AXObject* obj = get(node)) {
617 obj->selectionChanged();
618 return;
619 }
620 node = node->parentNode();
621 }
622 }
623
textChanged(Node * node)624 void AXObjectCache::textChanged(Node* node)
625 {
626 textChanged(getOrCreate(node));
627 }
628
textChanged(RenderObject * renderer)629 void AXObjectCache::textChanged(RenderObject* renderer)
630 {
631 textChanged(getOrCreate(renderer));
632 }
633
textChanged(AXObject * obj)634 void AXObjectCache::textChanged(AXObject* obj)
635 {
636 if (!obj)
637 return;
638
639 bool parentAlreadyExists = obj->parentObjectIfExists();
640 obj->textChanged();
641 postNotification(obj, obj->document(), AXObjectCache::AXTextChanged, true);
642 if (parentAlreadyExists)
643 obj->notifyIfIgnoredValueChanged();
644 }
645
updateCacheAfterNodeIsAttached(Node * node)646 void AXObjectCache::updateCacheAfterNodeIsAttached(Node* node)
647 {
648 // Calling get() will update the AX object if we had an AXNodeObject but now we need
649 // an AXRenderObject, because it was reparented to a location outside of a canvas.
650 get(node);
651 }
652
childrenChanged(Node * node)653 void AXObjectCache::childrenChanged(Node* node)
654 {
655 childrenChanged(get(node));
656 }
657
childrenChanged(RenderObject * renderer)658 void AXObjectCache::childrenChanged(RenderObject* renderer)
659 {
660 childrenChanged(get(renderer));
661 }
662
childrenChanged(AXObject * obj)663 void AXObjectCache::childrenChanged(AXObject* obj)
664 {
665 if (!obj)
666 return;
667
668 obj->childrenChanged();
669 }
670
notificationPostTimerFired(Timer<AXObjectCache> *)671 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
672 {
673 RefPtr<Document> protectorForCacheOwner(m_document);
674
675 m_notificationPostTimer.stop();
676
677 unsigned i = 0, count = m_notificationsToPost.size();
678 for (i = 0; i < count; ++i) {
679 AXObject* obj = m_notificationsToPost[i].first.get();
680 if (!obj->axObjectID())
681 continue;
682
683 if (!obj->axObjectCache())
684 continue;
685
686 #ifndef NDEBUG
687 // Make sure none of the render views are in the process of being layed out.
688 // Notifications should only be sent after the renderer has finished
689 if (obj->isAXRenderObject()) {
690 AXRenderObject* renderObj = toAXRenderObject(obj);
691 RenderObject* renderer = renderObj->renderer();
692 if (renderer && renderer->view())
693 ASSERT(!renderer->view()->layoutState());
694 }
695 #endif
696
697 AXNotification notification = m_notificationsToPost[i].second;
698 postPlatformNotification(obj, notification);
699
700 if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored())
701 childrenChanged(obj->parentObject());
702 }
703
704 m_notificationsToPost.clear();
705 }
706
postNotification(RenderObject * renderer,AXNotification notification,bool postToElement,PostType postType)707 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType)
708 {
709 if (!renderer)
710 return;
711
712 m_computedObjectAttributeCache->clear();
713
714 // Get an accessibility object that already exists. One should not be created here
715 // because a render update may be in progress and creating an AX object can re-trigger a layout
716 RefPtr<AXObject> object = get(renderer);
717 while (!object && renderer) {
718 renderer = renderer->parent();
719 object = get(renderer);
720 }
721
722 if (!renderer)
723 return;
724
725 postNotification(object.get(), &renderer->document(), notification, postToElement, postType);
726 }
727
postNotification(Node * node,AXNotification notification,bool postToElement,PostType postType)728 void AXObjectCache::postNotification(Node* node, AXNotification notification, bool postToElement, PostType postType)
729 {
730 if (!node)
731 return;
732
733 m_computedObjectAttributeCache->clear();
734
735 // Get an accessibility object that already exists. One should not be created here
736 // because a render update may be in progress and creating an AX object can re-trigger a layout
737 RefPtr<AXObject> object = get(node);
738 while (!object && node) {
739 node = node->parentNode();
740 object = get(node);
741 }
742
743 if (!node)
744 return;
745
746 postNotification(object.get(), &node->document(), notification, postToElement, postType);
747 }
748
postNotification(AXObject * object,Document * document,AXNotification notification,bool postToElement,PostType postType)749 void AXObjectCache::postNotification(AXObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType)
750 {
751 m_computedObjectAttributeCache->clear();
752
753 if (object && !postToElement)
754 object = object->observableObject();
755
756 if (!object && document)
757 object = get(document->renderer());
758
759 if (!object)
760 return;
761
762 if (postType == PostAsynchronously) {
763 m_notificationsToPost.append(std::make_pair(object, notification));
764 if (!m_notificationPostTimer.isActive())
765 m_notificationPostTimer.startOneShot(0);
766 } else {
767 postPlatformNotification(object, notification);
768 }
769 }
770
checkedStateChanged(Node * node)771 void AXObjectCache::checkedStateChanged(Node* node)
772 {
773 postNotification(node, AXObjectCache::AXCheckedStateChanged, true);
774 }
775
selectedChildrenChanged(Node * node)776 void AXObjectCache::selectedChildrenChanged(Node* node)
777 {
778 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree
779 // to find the container which should send out the notification.
780 postNotification(node, AXSelectedChildrenChanged, false);
781 }
782
selectedChildrenChanged(RenderObject * renderer)783 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
784 {
785 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree
786 // to find the container which should send out the notification.
787 postNotification(renderer, AXSelectedChildrenChanged, false);
788 }
789
handleScrollbarUpdate(ScrollView * view)790 void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
791 {
792 if (!view)
793 return;
794
795 // We don't want to create a scroll view from this method, only update an existing one.
796 if (AXObject* scrollViewObject = get(view)) {
797 m_computedObjectAttributeCache->clear();
798 scrollViewObject->updateChildrenIfNecessary();
799 }
800 }
801
handleAriaExpandedChange(Node * node)802 void AXObjectCache::handleAriaExpandedChange(Node* node)
803 {
804 if (AXObject* obj = getOrCreate(node))
805 obj->handleAriaExpandedChanged();
806 }
807
handleActiveDescendantChanged(Node * node)808 void AXObjectCache::handleActiveDescendantChanged(Node* node)
809 {
810 if (AXObject* obj = getOrCreate(node))
811 obj->handleActiveDescendantChanged();
812 }
813
handleAriaRoleChanged(Node * node)814 void AXObjectCache::handleAriaRoleChanged(Node* node)
815 {
816 if (AXObject* obj = getOrCreate(node)) {
817 obj->updateAccessibilityRole();
818 m_computedObjectAttributeCache->clear();
819 obj->notifyIfIgnoredValueChanged();
820 }
821 }
822
handleAttributeChanged(const QualifiedName & attrName,Element * element)823 void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element* element)
824 {
825 if (attrName == roleAttr)
826 handleAriaRoleChanged(element);
827 else if (attrName == altAttr || attrName == titleAttr)
828 textChanged(element);
829 else if (attrName == forAttr && isHTMLLabelElement(element))
830 labelChanged(element);
831
832 if (!attrName.localName().string().startsWith("aria-"))
833 return;
834
835 if (attrName == aria_activedescendantAttr)
836 handleActiveDescendantChanged(element);
837 else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr)
838 postNotification(element, AXObjectCache::AXValueChanged, true);
839 else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr)
840 textChanged(element);
841 else if (attrName == aria_checkedAttr)
842 checkedStateChanged(element);
843 else if (attrName == aria_selectedAttr)
844 selectedChildrenChanged(element);
845 else if (attrName == aria_expandedAttr)
846 handleAriaExpandedChange(element);
847 else if (attrName == aria_hiddenAttr)
848 childrenChanged(element->parentNode());
849 else if (attrName == aria_invalidAttr)
850 postNotification(element, AXObjectCache::AXInvalidStatusChanged, true);
851 else
852 postNotification(element, AXObjectCache::AXAriaAttributeChanged, true);
853 }
854
labelChanged(Element * element)855 void AXObjectCache::labelChanged(Element* element)
856 {
857 textChanged(toHTMLLabelElement(element)->control());
858 }
859
recomputeIsIgnored(RenderObject * renderer)860 void AXObjectCache::recomputeIsIgnored(RenderObject* renderer)
861 {
862 if (AXObject* obj = get(renderer))
863 obj->notifyIfIgnoredValueChanged();
864 }
865
startCachingComputedObjectAttributesUntilTreeMutates()866 void AXObjectCache::startCachingComputedObjectAttributesUntilTreeMutates()
867 {
868 // FIXME: no longer needed. When Chromium no longer calls
869 // WebAXObject::startCachingComputedObjectAttributesUntilTreeMutates,
870 // delete this function and the WebAXObject interfaces.
871 }
872
stopCachingComputedObjectAttributes()873 void AXObjectCache::stopCachingComputedObjectAttributes()
874 {
875 // FIXME: no longer needed (see above).
876 }
877
visiblePositionForTextMarkerData(TextMarkerData & textMarkerData)878 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
879 {
880 if (!isNodeInUse(textMarkerData.node))
881 return VisiblePosition();
882
883 // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects.
884 VisiblePosition visiblePos = VisiblePosition(createLegacyEditingPosition(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity);
885 Position deepPos = visiblePos.deepEquivalent();
886 if (deepPos.isNull())
887 return VisiblePosition();
888
889 RenderObject* renderer = deepPos.deprecatedNode()->renderer();
890 if (!renderer)
891 return VisiblePosition();
892
893 AXObjectCache* cache = renderer->document().axObjectCache();
894 if (!cache->isIDinUse(textMarkerData.axID))
895 return VisiblePosition();
896
897 if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
898 return VisiblePosition();
899
900 return visiblePos;
901 }
902
textMarkerDataForVisiblePosition(TextMarkerData & textMarkerData,const VisiblePosition & visiblePos)903 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
904 {
905 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
906 // This also allows callers to check for failure by looking at textMarkerData upon return.
907 memset(&textMarkerData, 0, sizeof(TextMarkerData));
908
909 if (visiblePos.isNull())
910 return;
911
912 Position deepPos = visiblePos.deepEquivalent();
913 Node* domNode = deepPos.deprecatedNode();
914 ASSERT(domNode);
915 if (!domNode)
916 return;
917
918 if (domNode->hasTagName(inputTag) && toHTMLInputElement(domNode)->isPasswordField())
919 return;
920
921 // find or create an accessibility object for this node
922 AXObjectCache* cache = domNode->document().axObjectCache();
923 RefPtr<AXObject> obj = cache->getOrCreate(domNode);
924
925 textMarkerData.axID = obj.get()->axObjectID();
926 textMarkerData.node = domNode;
927 textMarkerData.offset = deepPos.deprecatedEditingOffset();
928 textMarkerData.affinity = visiblePos.affinity();
929
930 cache->setNodeInUse(domNode);
931 }
932
rootAXEditableElement(const Node * node)933 const Element* AXObjectCache::rootAXEditableElement(const Node* node)
934 {
935 const Element* result = node->rootEditableElement();
936 const Element* element = node->isElementNode() ? toElement(node) : node->parentElement();
937
938 for (; element; element = element->parentElement()) {
939 if (nodeIsTextControl(element))
940 result = element;
941 }
942
943 return result;
944 }
945
nodeIsTextControl(const Node * node)946 bool AXObjectCache::nodeIsTextControl(const Node* node)
947 {
948 if (!node)
949 return false;
950
951 const AXObject* axObject = getOrCreate(const_cast<Node*>(node));
952 return axObject && axObject->isTextControl();
953 }
954
isNodeAriaVisible(Node * node)955 bool isNodeAriaVisible(Node* node)
956 {
957 if (!node)
958 return false;
959
960 if (!node->isElementNode())
961 return false;
962
963 return equalIgnoringCase(toElement(node)->getAttribute(aria_hiddenAttr), "false");
964 }
965
detachWrapper(AXObject * obj)966 void AXObjectCache::detachWrapper(AXObject* obj)
967 {
968 // In Chromium, AXObjects are not wrapped.
969 }
970
attachWrapper(AXObject *)971 void AXObjectCache::attachWrapper(AXObject*)
972 {
973 // In Chromium, AXObjects are not wrapped.
974 }
975
postPlatformNotification(AXObject * obj,AXNotification notification)976 void AXObjectCache::postPlatformNotification(AXObject* obj, AXNotification notification)
977 {
978 if (obj && obj->isAXScrollbar() && notification == AXValueChanged) {
979 // Send document value changed on scrollbar value changed notification.
980 Scrollbar* scrollBar = toAXScrollbar(obj)->scrollbar();
981 if (!scrollBar || !scrollBar->parent() || !scrollBar->parent()->isFrameView())
982 return;
983 Document* document = toFrameView(scrollBar->parent())->frame().document();
984 if (document != document->topDocument())
985 return;
986 obj = get(document->renderer());
987 }
988
989 if (!obj || !obj->document() || !obj->documentFrameView() || !obj->documentFrameView()->frame().page())
990 return;
991
992 ChromeClient& client = obj->documentFrameView()->frame().page()->chrome().client();
993
994 if (notification == AXActiveDescendantChanged
995 && obj->document()->focusedElement()
996 && obj->node() == obj->document()->focusedElement()) {
997 // Calling handleFocusedUIElementChanged will focus the new active
998 // descendant and send the AXFocusedUIElementChanged notification.
999 handleFocusedUIElementChanged(0, obj->document()->focusedElement());
1000 }
1001
1002 client.postAccessibilityNotification(obj, notification);
1003 }
1004
handleFocusedUIElementChanged(Node *,Node * newFocusedNode)1005 void AXObjectCache::handleFocusedUIElementChanged(Node*, Node* newFocusedNode)
1006 {
1007 if (!newFocusedNode)
1008 return;
1009
1010 Page* page = newFocusedNode->document().page();
1011 if (!page)
1012 return;
1013
1014 AXObject* focusedObject = focusedUIElementForPage(page);
1015 if (!focusedObject)
1016 return;
1017
1018 postPlatformNotification(focusedObject, AXFocusedUIElementChanged);
1019 }
1020
handleScrolledToAnchor(const Node * anchorNode)1021 void AXObjectCache::handleScrolledToAnchor(const Node* anchorNode)
1022 {
1023 // The anchor node may not be accessible. Post the notification for the
1024 // first accessible object.
1025 postPlatformNotification(AXObject::firstAccessibleObjectFromNode(anchorNode), AXScrolledToAnchor);
1026 }
1027
1028 } // namespace WebCore
1029