• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "config.h"
30 
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