OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2014, Google 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/AXObjectCacheImpl.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/FrameView.h" | |
60 #include "core/frame/LocalFrame.h" | |
61 #include "core/frame/Settings.h" | |
62 #include "core/html/HTMLAreaElement.h" | |
63 #include "core/html/HTMLImageElement.h" | |
64 #include "core/html/HTMLInputElement.h" | |
65 #include "core/html/HTMLLabelElement.h" | |
66 #include "core/page/Chrome.h" | |
67 #include "core/page/ChromeClient.h" | |
68 #include "core/page/FocusController.h" | |
69 #include "core/page/Page.h" | |
70 #include "core/rendering/AbstractInlineTextBox.h" | |
71 #include "core/rendering/RenderListBox.h" | |
72 #include "core/rendering/RenderMenuList.h" | |
73 #include "core/rendering/RenderProgress.h" | |
74 #include "core/rendering/RenderSlider.h" | |
75 #include "core/rendering/RenderTable.h" | |
76 #include "core/rendering/RenderTableCell.h" | |
77 #include "core/rendering/RenderTableRow.h" | |
78 #include "core/rendering/RenderView.h" | |
79 #include "wtf/PassRefPtr.h" | |
80 | |
81 namespace blink { | |
82 | |
83 using namespace HTMLNames; | |
84 | |
85 // static | |
86 AXObjectCache* AXObjectCache::create(Document& document) | |
87 { | |
88 return new AXObjectCacheImpl(document); | |
89 } | |
90 | |
91 AXObjectCacheImpl::AXObjectCacheImpl(Document& document) | |
92 : m_document(document) | |
93 , m_modificationCount(0) | |
94 , m_notificationPostTimer(this, &AXObjectCacheImpl::notificationPostTimerFir
ed) | |
95 { | |
96 } | |
97 | |
98 AXObjectCacheImpl::~AXObjectCacheImpl() | |
99 { | |
100 m_notificationPostTimer.stop(); | |
101 | |
102 HashMap<AXID, RefPtr<AXObject> >::iterator end = m_objects.end(); | |
103 for (HashMap<AXID, RefPtr<AXObject> >::iterator it = m_objects.begin(); it !
= end; ++it) { | |
104 AXObject* obj = (*it).value.get(); | |
105 detachWrapper(obj); | |
106 obj->detach(); | |
107 removeAXID(obj); | |
108 } | |
109 } | |
110 | |
111 AXObject* AXObjectCacheImpl::getOrCreateAXObjectFromRenderView(RenderView* rende
rView) | |
112 { | |
113 return getOrCreate(renderView); | |
114 } | |
115 | |
116 AXObject* AXObjectCacheImpl::root() | |
117 { | |
118 return getOrCreate(&m_document); | |
119 } | |
120 | |
121 AXObject* AXObjectCacheImpl::focusedImageMapUIElement(HTMLAreaElement* areaEleme
nt) | |
122 { | |
123 // Find the corresponding accessibility object for the HTMLAreaElement. This
should be | |
124 // in the list of children for its corresponding image. | |
125 if (!areaElement) | |
126 return 0; | |
127 | |
128 HTMLImageElement* imageElement = areaElement->imageElement(); | |
129 if (!imageElement) | |
130 return 0; | |
131 | |
132 AXObject* axRenderImage = toAXObjectCacheImpl(areaElement->document().axObje
ctCache())->getOrCreate(imageElement); | |
133 if (!axRenderImage) | |
134 return 0; | |
135 | |
136 AXObject::AccessibilityChildrenVector imageChildren = axRenderImage->childre
n(); | |
137 unsigned count = imageChildren.size(); | |
138 for (unsigned k = 0; k < count; ++k) { | |
139 AXObject* child = imageChildren[k].get(); | |
140 if (!child->isImageMapLink()) | |
141 continue; | |
142 | |
143 if (toAXImageMapLink(child)->areaElement() == areaElement) | |
144 return child; | |
145 } | |
146 | |
147 return 0; | |
148 } | |
149 | |
150 AXObject* AXObjectCacheImpl::focusedUIElementForPage(const Page* page) | |
151 { | |
152 if (!page->settings().accessibilityEnabled()) | |
153 return 0; | |
154 | |
155 // Cross-process accessibility is not yet implemented. | |
156 if (!page->focusController().focusedOrMainFrame()->isLocalFrame()) | |
157 return 0; | |
158 | |
159 // get the focused node in the page | |
160 Document* focusedDocument = toLocalFrame(page->focusController().focusedOrMa
inFrame())->document(); | |
161 Node* focusedNode = focusedDocument->focusedElement(); | |
162 if (!focusedNode) | |
163 focusedNode = focusedDocument; | |
164 | |
165 if (isHTMLAreaElement(*focusedNode)) | |
166 return focusedImageMapUIElement(toHTMLAreaElement(focusedNode)); | |
167 | |
168 AXObject* obj = toAXObjectCacheImpl(focusedNode->document().axObjectCache())
->getOrCreate(focusedNode); | |
169 if (!obj) | |
170 return 0; | |
171 | |
172 if (obj->shouldFocusActiveDescendant()) { | |
173 if (AXObject* descendant = obj->activeDescendant()) | |
174 obj = descendant; | |
175 } | |
176 | |
177 // the HTML element, for example, is focusable but has an AX object that is
ignored | |
178 if (obj->accessibilityIsIgnored()) | |
179 obj = obj->parentObjectUnignored(); | |
180 | |
181 return obj; | |
182 } | |
183 | |
184 AXObject* AXObjectCacheImpl::get(Widget* widget) | |
185 { | |
186 if (!widget) | |
187 return 0; | |
188 | |
189 AXID axID = m_widgetObjectMapping.get(widget); | |
190 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); | |
191 if (!axID) | |
192 return 0; | |
193 | |
194 return m_objects.get(axID); | |
195 } | |
196 | |
197 AXObject* AXObjectCacheImpl::get(RenderObject* renderer) | |
198 { | |
199 if (!renderer) | |
200 return 0; | |
201 | |
202 AXID axID = m_renderObjectMapping.get(renderer); | |
203 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); | |
204 if (!axID) | |
205 return 0; | |
206 | |
207 return m_objects.get(axID); | |
208 } | |
209 | |
210 AXObject* AXObjectCacheImpl::get(Node* node) | |
211 { | |
212 if (!node) | |
213 return 0; | |
214 | |
215 AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer(
)) : 0; | |
216 ASSERT(!HashTraits<AXID>::isDeletedValue(renderID)); | |
217 | |
218 AXID nodeID = m_nodeObjectMapping.get(node); | |
219 ASSERT(!HashTraits<AXID>::isDeletedValue(nodeID)); | |
220 | |
221 if (node->renderer() && nodeID && !renderID) { | |
222 // This can happen if an AXNodeObject is created for a node that's not | |
223 // rendered, but later something changes and it gets a renderer (like if
it's | |
224 // reparented). | |
225 remove(nodeID); | |
226 return 0; | |
227 } | |
228 | |
229 if (renderID) | |
230 return m_objects.get(renderID); | |
231 | |
232 if (!nodeID) | |
233 return 0; | |
234 | |
235 return m_objects.get(nodeID); | |
236 } | |
237 | |
238 AXObject* AXObjectCacheImpl::get(AbstractInlineTextBox* inlineTextBox) | |
239 { | |
240 if (!inlineTextBox) | |
241 return 0; | |
242 | |
243 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox); | |
244 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); | |
245 if (!axID) | |
246 return 0; | |
247 | |
248 return m_objects.get(axID); | |
249 } | |
250 | |
251 // FIXME: This probably belongs on Node. | |
252 // FIXME: This should take a const char*, but one caller passes nullAtom. | |
253 bool nodeHasRole(Node* node, const String& role) | |
254 { | |
255 if (!node || !node->isElementNode()) | |
256 return false; | |
257 | |
258 return equalIgnoringCase(toElement(node)->getAttribute(roleAttr), role); | |
259 } | |
260 | |
261 static PassRefPtr<AXObject> createFromRenderer(RenderObject* renderer) | |
262 { | |
263 // FIXME: How could renderer->node() ever not be an Element? | |
264 Node* node = renderer->node(); | |
265 | |
266 // If the node is aria role="list" or the aria role is empty and its a | |
267 // ul/ol/dl type (it shouldn't be a list if aria says otherwise). | |
268 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) | |
269 || (nodeHasRole(node, nullAtom) && (isHTMLUListElement(*node) || isHTMLO
ListElement(*node) || isHTMLDListElement(*node))))) | |
270 return AXList::create(renderer); | |
271 | |
272 // aria tables | |
273 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) | |
274 return AXARIAGrid::create(renderer); | |
275 if (nodeHasRole(node, "row")) | |
276 return AXARIAGridRow::create(renderer); | |
277 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || no
deHasRole(node, "rowheader")) | |
278 return AXARIAGridCell::create(renderer); | |
279 | |
280 // media controls | |
281 if (node && node->isMediaControlElement()) | |
282 return AccessibilityMediaControl::create(renderer); | |
283 | |
284 if (isHTMLOptionElement(node)) | |
285 return AXListBoxOption::create(renderer); | |
286 | |
287 if (renderer->isSVGRoot()) | |
288 return AXSVGRoot::create(renderer); | |
289 | |
290 if (renderer->isBoxModelObject()) { | |
291 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); | |
292 if (cssBox->isListBox()) | |
293 return AXListBox::create(toRenderListBox(cssBox)); | |
294 if (cssBox->isMenuList()) | |
295 return AXMenuList::create(toRenderMenuList(cssBox)); | |
296 | |
297 // standard tables | |
298 if (cssBox->isTable()) | |
299 return AXTable::create(toRenderTable(cssBox)); | |
300 if (cssBox->isTableRow()) | |
301 return AXTableRow::create(toRenderTableRow(cssBox)); | |
302 if (cssBox->isTableCell()) | |
303 return AXTableCell::create(toRenderTableCell(cssBox)); | |
304 | |
305 // progress bar | |
306 if (cssBox->isProgress()) | |
307 return AXProgressIndicator::create(toRenderProgress(cssBox)); | |
308 | |
309 // input type=range | |
310 if (cssBox->isSlider()) | |
311 return AXSlider::create(toRenderSlider(cssBox)); | |
312 } | |
313 | |
314 return AXRenderObject::create(renderer); | |
315 } | |
316 | |
317 static PassRefPtr<AXObject> createFromNode(Node* node) | |
318 { | |
319 return AXNodeObject::create(node); | |
320 } | |
321 | |
322 static PassRefPtr<AXObject> createFromInlineTextBox(AbstractInlineTextBox* inlin
eTextBox) | |
323 { | |
324 return AXInlineTextBox::create(inlineTextBox); | |
325 } | |
326 | |
327 AXObject* AXObjectCacheImpl::getOrCreate(Widget* widget) | |
328 { | |
329 if (!widget) | |
330 return 0; | |
331 | |
332 if (AXObject* obj = get(widget)) | |
333 return obj; | |
334 | |
335 RefPtr<AXObject> newObj = nullptr; | |
336 if (widget->isFrameView()) | |
337 newObj = AXScrollView::create(toFrameView(widget)); | |
338 else if (widget->isScrollbar()) | |
339 newObj = AXScrollbar::create(toScrollbar(widget)); | |
340 | |
341 // Will crash later if we have two objects for the same widget. | |
342 ASSERT(!get(widget)); | |
343 | |
344 // Catch the case if an (unsupported) widget type is used. Only FrameView an
d ScrollBar are supported now. | |
345 ASSERT(newObj); | |
346 if (!newObj) | |
347 return 0; | |
348 | |
349 getAXID(newObj.get()); | |
350 | |
351 m_widgetObjectMapping.set(widget, newObj->axObjectID()); | |
352 m_objects.set(newObj->axObjectID(), newObj); | |
353 newObj->init(); | |
354 attachWrapper(newObj.get()); | |
355 return newObj.get(); | |
356 } | |
357 | |
358 AXObject* AXObjectCacheImpl::getOrCreate(Node* node) | |
359 { | |
360 if (!node) | |
361 return 0; | |
362 | |
363 if (AXObject* obj = get(node)) | |
364 return obj; | |
365 | |
366 if (node->renderer()) | |
367 return getOrCreate(node->renderer()); | |
368 | |
369 if (!node->parentElement()) | |
370 return 0; | |
371 | |
372 // It's only allowed to create an AXObject from a Node if it's in a canvas s
ubtree. | |
373 // Or if it's a hidden element, but we still want to expose it because of ot
her ARIA attributes. | |
374 bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree(); | |
375 bool isHidden = !node->renderer() && isNodeAriaVisible(node); | |
376 if (!inCanvasSubtree && !isHidden) | |
377 return 0; | |
378 | |
379 RefPtr<AXObject> newObj = createFromNode(node); | |
380 | |
381 // Will crash later if we have two objects for the same node. | |
382 ASSERT(!get(node)); | |
383 | |
384 getAXID(newObj.get()); | |
385 | |
386 m_nodeObjectMapping.set(node, newObj->axObjectID()); | |
387 m_objects.set(newObj->axObjectID(), newObj); | |
388 newObj->init(); | |
389 attachWrapper(newObj.get()); | |
390 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); | |
391 | |
392 return newObj.get(); | |
393 } | |
394 | |
395 AXObject* AXObjectCacheImpl::getOrCreate(RenderObject* renderer) | |
396 { | |
397 if (!renderer) | |
398 return 0; | |
399 | |
400 if (AXObject* obj = get(renderer)) | |
401 return obj; | |
402 | |
403 RefPtr<AXObject> newObj = createFromRenderer(renderer); | |
404 | |
405 // Will crash later if we have two objects for the same renderer. | |
406 ASSERT(!get(renderer)); | |
407 | |
408 getAXID(newObj.get()); | |
409 | |
410 m_renderObjectMapping.set(renderer, newObj->axObjectID()); | |
411 m_objects.set(newObj->axObjectID(), newObj); | |
412 newObj->init(); | |
413 attachWrapper(newObj.get()); | |
414 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); | |
415 | |
416 return newObj.get(); | |
417 } | |
418 | |
419 AXObject* AXObjectCacheImpl::getOrCreate(AbstractInlineTextBox* inlineTextBox) | |
420 { | |
421 if (!inlineTextBox) | |
422 return 0; | |
423 | |
424 if (AXObject* obj = get(inlineTextBox)) | |
425 return obj; | |
426 | |
427 RefPtr<AXObject> newObj = createFromInlineTextBox(inlineTextBox); | |
428 | |
429 // Will crash later if we have two objects for the same inlineTextBox. | |
430 ASSERT(!get(inlineTextBox)); | |
431 | |
432 getAXID(newObj.get()); | |
433 | |
434 m_inlineTextBoxObjectMapping.set(inlineTextBox, newObj->axObjectID()); | |
435 m_objects.set(newObj->axObjectID(), newObj); | |
436 newObj->init(); | |
437 attachWrapper(newObj.get()); | |
438 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); | |
439 | |
440 return newObj.get(); | |
441 } | |
442 | |
443 AXObject* AXObjectCacheImpl::rootObject() | |
444 { | |
445 if (!accessibilityEnabled()) | |
446 return 0; | |
447 | |
448 return getOrCreate(m_document.view()); | |
449 } | |
450 | |
451 AXObject* AXObjectCacheImpl::getOrCreate(AccessibilityRole role) | |
452 { | |
453 RefPtr<AXObject> obj = nullptr; | |
454 | |
455 // will be filled in... | |
456 switch (role) { | |
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 = nullptr; | |
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 | |
496 void AXObjectCacheImpl::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 | |
517 void AXObjectCacheImpl::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 | |
527 void AXObjectCacheImpl::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 | |
545 void AXObjectCacheImpl::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 | |
555 void AXObjectCacheImpl::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 | |
565 // FIXME: Oilpan: Use a weak hashmap for this instead. | |
566 void AXObjectCacheImpl::clearWeakMembers(Visitor* visitor) | |
567 { | |
568 Vector<Node*> deadNodes; | |
569 for (HashMap<Node*, AXID>::iterator it = m_nodeObjectMapping.begin(); it !=
m_nodeObjectMapping.end(); ++it) { | |
570 if (!visitor->isAlive(it->key)) | |
571 deadNodes.append(it->key); | |
572 } | |
573 for (unsigned i = 0; i < deadNodes.size(); ++i) | |
574 remove(deadNodes[i]); | |
575 | |
576 Vector<Widget*> deadWidgets; | |
577 for (HashMap<Widget*, AXID>::iterator it = m_widgetObjectMapping.begin(); | |
578 it != m_widgetObjectMapping.end(); ++it) { | |
579 if (!visitor->isAlive(it->key)) | |
580 deadWidgets.append(it->key); | |
581 } | |
582 for (unsigned i = 0; i < deadWidgets.size(); ++i) | |
583 remove(deadWidgets[i]); | |
584 } | |
585 | |
586 AXID AXObjectCacheImpl::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.con
tains(objID)); | |
595 | |
596 lastUsedID = objID; | |
597 | |
598 return objID; | |
599 } | |
600 | |
601 AXID AXObjectCacheImpl::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 | |
618 void AXObjectCacheImpl::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 | |
632 void AXObjectCacheImpl::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 | |
645 void AXObjectCacheImpl::textChanged(Node* node) | |
646 { | |
647 textChanged(getOrCreate(node)); | |
648 } | |
649 | |
650 void AXObjectCacheImpl::textChanged(RenderObject* renderer) | |
651 { | |
652 textChanged(getOrCreate(renderer)); | |
653 } | |
654 | |
655 void AXObjectCacheImpl::textChanged(AXObject* obj) | |
656 { | |
657 if (!obj) | |
658 return; | |
659 | |
660 bool parentAlreadyExists = obj->parentObjectIfExists(); | |
661 obj->textChanged(); | |
662 postNotification(obj, obj->document(), AXObjectCacheImpl::AXTextChanged, tru
e); | |
663 if (parentAlreadyExists) | |
664 obj->notifyIfIgnoredValueChanged(); | |
665 } | |
666 | |
667 void AXObjectCacheImpl::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 c
anvas. | |
671 get(node); | |
672 } | |
673 | |
674 void AXObjectCacheImpl::childrenChanged(Node* node) | |
675 { | |
676 childrenChanged(get(node)); | |
677 } | |
678 | |
679 void AXObjectCacheImpl::childrenChanged(RenderObject* renderer) | |
680 { | |
681 childrenChanged(get(renderer)); | |
682 } | |
683 | |
684 void AXObjectCacheImpl::childrenChanged(Widget* widget) | |
685 { | |
686 childrenChanged(get(widget)); | |
687 } | |
688 | |
689 void AXObjectCacheImpl::childrenChanged(AXObject* obj) | |
690 { | |
691 if (!obj) | |
692 return; | |
693 | |
694 obj->childrenChanged(); | |
695 } | |
696 | |
697 void AXObjectCacheImpl::notificationPostTimerFired(Timer<AXObjectCacheImpl>*) | |
698 { | |
699 RefPtrWillBeRawPtr<Document> protectorForCacheOwner(m_document); | |
700 | |
701 m_notificationPostTimer.stop(); | |
702 | |
703 unsigned i = 0, count = m_notificationsToPost.size(); | |
704 for (i = 0; i < count; ++i) { | |
705 AXObject* obj = m_notificationsToPost[i].first.get(); | |
706 if (!obj->axObjectID()) | |
707 continue; | |
708 | |
709 if (!obj->axObjectCache()) | |
710 continue; | |
711 | |
712 #if ENABLE(ASSERT) | |
713 // Make sure none of the render views are in the process of being layed
out. | |
714 // Notifications should only be sent after the renderer has finished | |
715 if (obj->isAXRenderObject()) { | |
716 AXRenderObject* renderObj = toAXRenderObject(obj); | |
717 RenderObject* renderer = renderObj->renderer(); | |
718 if (renderer && renderer->view()) | |
719 ASSERT(!renderer->view()->layoutState()); | |
720 } | |
721 #endif | |
722 | |
723 AXNotification notification = m_notificationsToPost[i].second; | |
724 postPlatformNotification(obj, notification); | |
725 | |
726 if (notification == AXChildrenChanged && obj->parentObjectIfExists() &&
obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored()) | |
727 childrenChanged(obj->parentObject()); | |
728 } | |
729 | |
730 m_notificationsToPost.clear(); | |
731 } | |
732 | |
733 void AXObjectCacheImpl::postNotification(RenderObject* renderer, AXNotification
notification, bool postToElement) | |
734 { | |
735 if (!renderer) | |
736 return; | |
737 | |
738 m_modificationCount++; | |
739 | |
740 // Get an accessibility object that already exists. One should not be create
d here | |
741 // because a render update may be in progress and creating an AX object can
re-trigger a layout | |
742 RefPtr<AXObject> object = get(renderer); | |
743 while (!object && renderer) { | |
744 renderer = renderer->parent(); | |
745 object = get(renderer); | |
746 } | |
747 | |
748 if (!renderer) | |
749 return; | |
750 | |
751 postNotification(object.get(), &renderer->document(), notification, postToEl
ement); | |
752 } | |
753 | |
754 void AXObjectCacheImpl::postNotification(Node* node, AXNotification notification
, bool postToElement) | |
755 { | |
756 if (!node) | |
757 return; | |
758 | |
759 m_modificationCount++; | |
760 | |
761 // Get an accessibility object that already exists. One should not be create
d here | |
762 // because a render update may be in progress and creating an AX object can
re-trigger a layout | |
763 RefPtr<AXObject> object = get(node); | |
764 while (!object && node) { | |
765 node = node->parentNode(); | |
766 object = get(node); | |
767 } | |
768 | |
769 if (!node) | |
770 return; | |
771 | |
772 postNotification(object.get(), &node->document(), notification, postToElemen
t); | |
773 } | |
774 | |
775 void AXObjectCacheImpl::postNotification(AXObject* object, Document* document, A
XNotification notification, bool postToElement) | |
776 { | |
777 m_modificationCount++; | |
778 | |
779 if (object && !postToElement) | |
780 object = object->observableObject(); | |
781 | |
782 if (!object && document) | |
783 object = get(document->renderView()); | |
784 | |
785 if (!object) | |
786 return; | |
787 | |
788 m_notificationsToPost.append(std::make_pair(object, notification)); | |
789 if (!m_notificationPostTimer.isActive()) | |
790 m_notificationPostTimer.startOneShot(0, FROM_HERE); | |
791 } | |
792 | |
793 void AXObjectCacheImpl::checkedStateChanged(Node* node) | |
794 { | |
795 postNotification(node, AXObjectCacheImpl::AXCheckedStateChanged, true); | |
796 } | |
797 | |
798 void AXObjectCacheImpl::selectedChildrenChanged(Node* node) | |
799 { | |
800 // postToElement is false so that you can pass in any child of an element an
d it will go up the parent tree | |
801 // to find the container which should send out the notification. | |
802 postNotification(node, AXSelectedChildrenChanged, false); | |
803 } | |
804 | |
805 void AXObjectCacheImpl::selectedChildrenChanged(RenderObject* renderer) | |
806 { | |
807 // postToElement is false so that you can pass in any child of an element an
d it will go up the parent tree | |
808 // to find the container which should send out the notification. | |
809 postNotification(renderer, AXSelectedChildrenChanged, false); | |
810 } | |
811 | |
812 void AXObjectCacheImpl::handleScrollbarUpdate(FrameView* view) | |
813 { | |
814 if (!view) | |
815 return; | |
816 | |
817 // We don't want to create a scroll view from this method, only update an ex
isting one. | |
818 if (AXObject* scrollViewObject = get(view)) { | |
819 m_modificationCount++; | |
820 scrollViewObject->updateChildrenIfNecessary(); | |
821 } | |
822 } | |
823 | |
824 void AXObjectCacheImpl::handleLayoutComplete(RenderObject* renderer) | |
825 { | |
826 if (!renderer) | |
827 return; | |
828 | |
829 m_modificationCount++; | |
830 | |
831 // Create the AXObject if it didn't yet exist - that's always safe at the en
d of a layout, and it | |
832 // allows an AX notification to be sent when a page has its first layout, ra
ther than when the | |
833 // document first loads. | |
834 if (AXObject* obj = getOrCreate(renderer)) | |
835 postNotification(obj, obj->document(), AXLayoutComplete, true); | |
836 } | |
837 | |
838 void AXObjectCacheImpl::handleAriaExpandedChange(Node* node) | |
839 { | |
840 if (AXObject* obj = getOrCreate(node)) | |
841 obj->handleAriaExpandedChanged(); | |
842 } | |
843 | |
844 void AXObjectCacheImpl::handleActiveDescendantChanged(Node* node) | |
845 { | |
846 if (AXObject* obj = getOrCreate(node)) | |
847 obj->handleActiveDescendantChanged(); | |
848 } | |
849 | |
850 void AXObjectCacheImpl::handleAriaRoleChanged(Node* node) | |
851 { | |
852 if (AXObject* obj = getOrCreate(node)) { | |
853 obj->updateAccessibilityRole(); | |
854 m_modificationCount++; | |
855 obj->notifyIfIgnoredValueChanged(); | |
856 } | |
857 } | |
858 | |
859 void AXObjectCacheImpl::handleAttributeChanged(const QualifiedName& attrName, El
ement* element) | |
860 { | |
861 if (attrName == roleAttr) | |
862 handleAriaRoleChanged(element); | |
863 else if (attrName == altAttr || attrName == titleAttr) | |
864 textChanged(element); | |
865 else if (attrName == forAttr && isHTMLLabelElement(*element)) | |
866 labelChanged(element); | |
867 | |
868 if (!attrName.localName().startsWith("aria-")) | |
869 return; | |
870 | |
871 if (attrName == aria_activedescendantAttr) | |
872 handleActiveDescendantChanged(element); | |
873 else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr) | |
874 postNotification(element, AXObjectCacheImpl::AXValueChanged, true); | |
875 else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || att
rName == aria_labelledbyAttr) | |
876 textChanged(element); | |
877 else if (attrName == aria_checkedAttr) | |
878 checkedStateChanged(element); | |
879 else if (attrName == aria_selectedAttr) | |
880 selectedChildrenChanged(element); | |
881 else if (attrName == aria_expandedAttr) | |
882 handleAriaExpandedChange(element); | |
883 else if (attrName == aria_hiddenAttr) | |
884 childrenChanged(element->parentNode()); | |
885 else if (attrName == aria_invalidAttr) | |
886 postNotification(element, AXObjectCacheImpl::AXInvalidStatusChanged, tru
e); | |
887 else | |
888 postNotification(element, AXObjectCacheImpl::AXAriaAttributeChanged, tru
e); | |
889 } | |
890 | |
891 void AXObjectCacheImpl::labelChanged(Element* element) | |
892 { | |
893 textChanged(toHTMLLabelElement(element)->control()); | |
894 } | |
895 | |
896 void AXObjectCacheImpl::recomputeIsIgnored(RenderObject* renderer) | |
897 { | |
898 if (AXObject* obj = get(renderer)) | |
899 obj->notifyIfIgnoredValueChanged(); | |
900 } | |
901 | |
902 void AXObjectCacheImpl::inlineTextBoxesUpdated(RenderObject* renderer) | |
903 { | |
904 if (!inlineTextBoxAccessibilityEnabled()) | |
905 return; | |
906 | |
907 // Only update if the accessibility object already exists and it's | |
908 // not already marked as dirty. | |
909 if (AXObject* obj = get(renderer)) { | |
910 if (!obj->needsToUpdateChildren()) { | |
911 obj->setNeedsToUpdateChildren(); | |
912 postNotification(renderer, AXChildrenChanged, true); | |
913 } | |
914 } | |
915 } | |
916 | |
917 Settings* AXObjectCacheImpl::settings() | |
918 { | |
919 return m_document.settings(); | |
920 } | |
921 | |
922 bool AXObjectCacheImpl::accessibilityEnabled() | |
923 { | |
924 Settings* settings = this->settings(); | |
925 if (!settings) | |
926 return false; | |
927 return settings->accessibilityEnabled(); | |
928 } | |
929 | |
930 bool AXObjectCacheImpl::inlineTextBoxAccessibilityEnabled() | |
931 { | |
932 Settings* settings = this->settings(); | |
933 if (!settings) | |
934 return false; | |
935 return settings->inlineTextBoxAccessibilityEnabled(); | |
936 } | |
937 | |
938 const Element* AXObjectCacheImpl::rootAXEditableElement(const Node* node) | |
939 { | |
940 const Element* result = node->rootEditableElement(); | |
941 const Element* element = node->isElementNode() ? toElement(node) : node->par
entElement(); | |
942 | |
943 for (; element; element = element->parentElement()) { | |
944 if (nodeIsTextControl(element)) | |
945 result = element; | |
946 } | |
947 | |
948 return result; | |
949 } | |
950 | |
951 bool AXObjectCacheImpl::nodeIsTextControl(const Node* node) | |
952 { | |
953 if (!node) | |
954 return false; | |
955 | |
956 const AXObject* axObject = getOrCreate(const_cast<Node*>(node)); | |
957 return axObject && axObject->isTextControl(); | |
958 } | |
959 | |
960 bool isNodeAriaVisible(Node* node) | |
961 { | |
962 if (!node) | |
963 return false; | |
964 | |
965 if (!node->isElementNode()) | |
966 return false; | |
967 | |
968 return equalIgnoringCase(toElement(node)->getAttribute(aria_hiddenAttr), "fa
lse"); | |
969 } | |
970 | |
971 void AXObjectCacheImpl::detachWrapper(AXObject* obj) | |
972 { | |
973 // In Chromium, AXObjects are not wrapped. | |
974 } | |
975 | |
976 void AXObjectCacheImpl::attachWrapper(AXObject*) | |
977 { | |
978 // In Chromium, AXObjects are not wrapped. | |
979 } | |
980 | |
981 void AXObjectCacheImpl::postPlatformNotification(AXObject* obj, AXNotification n
otification) | |
982 { | |
983 if (obj && obj->isAXScrollbar() && notification == AXValueChanged) { | |
984 // Send document value changed on scrollbar value changed notification. | |
985 Scrollbar* scrollBar = toAXScrollbar(obj)->scrollbar(); | |
986 if (!scrollBar || !scrollBar->parent() || !scrollBar->parent()->isFrameV
iew()) | |
987 return; | |
988 Document* document = toFrameView(scrollBar->parent())->frame().document(
); | |
989 if (document != document->topDocument()) | |
990 return; | |
991 obj = get(document->renderView()); | |
992 } | |
993 | |
994 if (!obj || !obj->document() || !obj->documentFrameView() || !obj->documentF
rameView()->frame().page()) | |
995 return; | |
996 | |
997 ChromeClient& client = obj->document()->axObjectCacheOwner().page()->chrome(
).client(); | |
998 | |
999 if (notification == AXActiveDescendantChanged | |
1000 && obj->document()->focusedElement() | |
1001 && obj->node() == obj->document()->focusedElement()) { | |
1002 // Calling handleFocusedUIElementChanged will focus the new active | |
1003 // descendant and send the AXFocusedUIElementChanged notification. | |
1004 handleFocusedUIElementChanged(0, obj->document()->focusedElement()); | |
1005 } | |
1006 | |
1007 client.postAccessibilityNotification(obj, notification); | |
1008 } | |
1009 | |
1010 void AXObjectCacheImpl::handleFocusedUIElementChanged(Node*, Node* newFocusedNod
e) | |
1011 { | |
1012 if (!newFocusedNode) | |
1013 return; | |
1014 | |
1015 Page* page = newFocusedNode->document().page(); | |
1016 if (!page) | |
1017 return; | |
1018 | |
1019 AXObject* focusedObject = focusedUIElementForPage(page); | |
1020 if (!focusedObject) | |
1021 return; | |
1022 | |
1023 postPlatformNotification(focusedObject, AXFocusedUIElementChanged); | |
1024 } | |
1025 | |
1026 void AXObjectCacheImpl::handleInitialFocus() | |
1027 { | |
1028 postNotification(&m_document, AXObjectCache::AXFocusedUIElementChanged, true
); | |
1029 } | |
1030 | |
1031 void AXObjectCacheImpl::handleEditableTextContentChanged(Node* node) | |
1032 { | |
1033 postNotification(node, AXObjectCache::AXValueChanged, false); | |
1034 } | |
1035 | |
1036 void AXObjectCacheImpl::handleTextFormControlChanged(Node* node) | |
1037 { | |
1038 postNotification(node, AXObjectCache::AXValueChanged, false); | |
1039 } | |
1040 | |
1041 void AXObjectCacheImpl::handleValueChanged(Node* node) | |
1042 { | |
1043 postNotification(node, AXObjectCache::AXValueChanged, true); | |
1044 } | |
1045 | |
1046 void AXObjectCacheImpl::handleUpdateActiveMenuOption(RenderMenuList* menuList, i
nt optionIndex) | |
1047 { | |
1048 if (AXMenuList* axMenuList = static_cast<AXMenuList*>(get(menuList))) | |
1049 axMenuList->didUpdateActiveOption(optionIndex); | |
1050 } | |
1051 | |
1052 void AXObjectCacheImpl::handleLoadComplete(Document* document) | |
1053 { | |
1054 postNotification(getOrCreate(document), document, AXObjectCache::AXLoadCompl
ete, true); | |
1055 } | |
1056 | |
1057 void AXObjectCacheImpl::handleLayoutComplete(Document* document) | |
1058 { | |
1059 postNotification(getOrCreate(document), document, AXObjectCache::AXLayoutCom
plete, true); | |
1060 } | |
1061 | |
1062 void AXObjectCacheImpl::handleScrolledToAnchor(const Node* anchorNode) | |
1063 { | |
1064 // The anchor node may not be accessible. Post the notification for the | |
1065 // first accessible object. | |
1066 postPlatformNotification(AXObject::firstAccessibleObjectFromNode(anchorNode)
, AXScrolledToAnchor); | |
1067 } | |
1068 | |
1069 void AXObjectCacheImpl::handleScrollPositionChanged(FrameView* frameView) | |
1070 { | |
1071 // Prefer to fire the scroll position changed event on the frame view's chil
d web area, if possible. | |
1072 AXObject* targetAXObject = getOrCreate(frameView); | |
1073 if (targetAXObject && !targetAXObject->children().isEmpty()) | |
1074 targetAXObject = targetAXObject->children()[0].get(); | |
1075 postPlatformNotification(targetAXObject, AXScrollPositionChanged); | |
1076 } | |
1077 | |
1078 void AXObjectCacheImpl::handleScrollPositionChanged(RenderObject* renderObject) | |
1079 { | |
1080 postPlatformNotification(getOrCreate(renderObject), AXScrollPositionChanged)
; | |
1081 } | |
1082 | |
1083 void AXObjectCacheImpl::setCanvasObjectBounds(Element* element, const LayoutRect
& rect) | |
1084 { | |
1085 AXObject* obj = getOrCreate(element); | |
1086 if (!obj) | |
1087 return; | |
1088 | |
1089 obj->setElementRect(rect); | |
1090 } | |
1091 | |
1092 } // namespace blink | |
OLD | NEW |