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