1 /*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "AccessibilityRenderObject.h"
31
32 #include "AXObjectCache.h"
33 #include "AccessibilityImageMapLink.h"
34 #include "AccessibilityListBox.h"
35 #include "CharacterNames.h"
36 #include "EventNames.h"
37 #include "FloatRect.h"
38 #include "Frame.h"
39 #include "FrameLoader.h"
40 #include "HTMLAreaElement.h"
41 #include "HTMLFormElement.h"
42 #include "HTMLFrameElementBase.h"
43 #include "HTMLImageElement.h"
44 #include "HTMLInputElement.h"
45 #include "HTMLLabelElement.h"
46 #include "HTMLMapElement.h"
47 #include "HTMLOptGroupElement.h"
48 #include "HTMLOptionElement.h"
49 #include "HTMLOptionsCollection.h"
50 #include "HTMLSelectElement.h"
51 #include "HTMLTextAreaElement.h"
52 #include "HitTestRequest.h"
53 #include "HitTestResult.h"
54 #include "LocalizedStrings.h"
55 #include "NodeList.h"
56 #include "ProgressTracker.h"
57 #include "RenderButton.h"
58 #include "RenderFieldset.h"
59 #include "RenderFileUploadControl.h"
60 #include "RenderHTMLCanvas.h"
61 #include "RenderImage.h"
62 #include "RenderInline.h"
63 #include "RenderListBox.h"
64 #include "RenderListMarker.h"
65 #include "RenderMenuList.h"
66 #include "RenderText.h"
67 #include "RenderTextControl.h"
68 #include "RenderTextFragment.h"
69 #include "RenderTheme.h"
70 #include "RenderView.h"
71 #include "RenderWidget.h"
72 #include "SelectionController.h"
73 #include "Text.h"
74 #include "TextIterator.h"
75 #include "htmlediting.h"
76 #include "visible_units.h"
77 #include <wtf/StdLibExtras.h>
78
79 using namespace std;
80
81 namespace WebCore {
82
83 using namespace HTMLNames;
84
AccessibilityRenderObject(RenderObject * renderer)85 AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer)
86 : AccessibilityObject()
87 , m_renderer(renderer)
88 , m_ariaRole(UnknownRole)
89 , m_childrenDirty(false)
90 , m_roleForMSAA(UnknownRole)
91 {
92 updateAccessibilityRole();
93 #ifndef NDEBUG
94 m_renderer->setHasAXObject(true);
95 #endif
96 }
97
~AccessibilityRenderObject()98 AccessibilityRenderObject::~AccessibilityRenderObject()
99 {
100 ASSERT(isDetached());
101 }
102
create(RenderObject * renderer)103 PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer)
104 {
105 return adoptRef(new AccessibilityRenderObject(renderer));
106 }
107
detach()108 void AccessibilityRenderObject::detach()
109 {
110 clearChildren();
111 AccessibilityObject::detach();
112
113 #ifndef NDEBUG
114 if (m_renderer)
115 m_renderer->setHasAXObject(false);
116 #endif
117 m_renderer = 0;
118 }
119
firstChild() const120 AccessibilityObject* AccessibilityRenderObject::firstChild() const
121 {
122 if (!m_renderer)
123 return 0;
124
125 RenderObject* firstChild = m_renderer->firstChild();
126 if (!firstChild)
127 return 0;
128
129 return m_renderer->document()->axObjectCache()->getOrCreate(firstChild);
130 }
131
lastChild() const132 AccessibilityObject* AccessibilityRenderObject::lastChild() const
133 {
134 if (!m_renderer)
135 return 0;
136
137 RenderObject* lastChild = m_renderer->lastChild();
138 if (!lastChild)
139 return 0;
140
141 return m_renderer->document()->axObjectCache()->getOrCreate(lastChild);
142 }
143
previousSibling() const144 AccessibilityObject* AccessibilityRenderObject::previousSibling() const
145 {
146 if (!m_renderer)
147 return 0;
148
149 RenderObject* previousSibling = m_renderer->previousSibling();
150 if (!previousSibling)
151 return 0;
152
153 return m_renderer->document()->axObjectCache()->getOrCreate(previousSibling);
154 }
155
nextSibling() const156 AccessibilityObject* AccessibilityRenderObject::nextSibling() const
157 {
158 if (!m_renderer)
159 return 0;
160
161 RenderObject* nextSibling = m_renderer->nextSibling();
162 if (!nextSibling)
163 return 0;
164
165 return m_renderer->document()->axObjectCache()->getOrCreate(nextSibling);
166 }
167
parentObjectIfExists() const168 AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const
169 {
170 if (!m_renderer)
171 return 0;
172
173 RenderObject* parent = m_renderer->parent();
174 if (!parent)
175 return 0;
176
177 return m_renderer->document()->axObjectCache()->get(parent);
178 }
179
parentObject() const180 AccessibilityObject* AccessibilityRenderObject::parentObject() const
181 {
182 if (!m_renderer)
183 return 0;
184
185 RenderObject* parent = m_renderer->parent();
186 if (!parent)
187 return 0;
188
189 if (ariaRoleAttribute() == MenuBarRole)
190 return m_renderer->document()->axObjectCache()->getOrCreate(parent);
191
192 // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child
193 if (ariaRoleAttribute() == MenuRole) {
194 AccessibilityObject* parent = menuButtonForMenu();
195 if (parent)
196 return parent;
197 }
198
199 return m_renderer->document()->axObjectCache()->getOrCreate(parent);
200 }
201
isWebArea() const202 bool AccessibilityRenderObject::isWebArea() const
203 {
204 return roleValue() == WebAreaRole;
205 }
206
isImageButton() const207 bool AccessibilityRenderObject::isImageButton() const
208 {
209 return isNativeImage() && roleValue() == ButtonRole;
210 }
211
isAnchor() const212 bool AccessibilityRenderObject::isAnchor() const
213 {
214 return !isNativeImage() && isLink();
215 }
216
isNativeTextControl() const217 bool AccessibilityRenderObject::isNativeTextControl() const
218 {
219 return m_renderer->isTextControl();
220 }
221
isTextControl() const222 bool AccessibilityRenderObject::isTextControl() const
223 {
224 AccessibilityRole role = roleValue();
225 return role == TextAreaRole || role == TextFieldRole;
226 }
227
isNativeImage() const228 bool AccessibilityRenderObject::isNativeImage() const
229 {
230 return m_renderer->isImage();
231 }
232
isImage() const233 bool AccessibilityRenderObject::isImage() const
234 {
235 return roleValue() == ImageRole;
236 }
237
isAttachment() const238 bool AccessibilityRenderObject::isAttachment() const
239 {
240 if (!m_renderer)
241 return false;
242
243 // Widgets are the replaced elements that we represent to AX as attachments
244 bool isWidget = m_renderer && m_renderer->isWidget();
245 ASSERT(!isWidget || (m_renderer->isReplaced() && !isImage()));
246 return isWidget && ariaRoleAttribute() == UnknownRole;
247 }
248
isPasswordField() const249 bool AccessibilityRenderObject::isPasswordField() const
250 {
251 ASSERT(m_renderer);
252 if (!m_renderer->node() || !m_renderer->node()->isHTMLElement())
253 return false;
254 if (ariaRoleAttribute() != UnknownRole)
255 return false;
256
257 InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
258 if (!inputElement)
259 return false;
260
261 return inputElement->isPasswordField();
262 }
263
isCheckboxOrRadio() const264 bool AccessibilityRenderObject::isCheckboxOrRadio() const
265 {
266 AccessibilityRole role = roleValue();
267 return role == RadioButtonRole || role == CheckBoxRole;
268 }
269
isFileUploadButton() const270 bool AccessibilityRenderObject::isFileUploadButton() const
271 {
272 if (m_renderer && m_renderer->node() && m_renderer->node()->hasTagName(inputTag)) {
273 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node());
274 return input->inputType() == HTMLInputElement::FILE;
275 }
276
277 return false;
278 }
279
isInputImage() const280 bool AccessibilityRenderObject::isInputImage() const
281 {
282 if (m_renderer && m_renderer->node() && m_renderer->node()->hasTagName(inputTag)) {
283 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node());
284 return input->inputType() == HTMLInputElement::IMAGE;
285 }
286
287 return false;
288 }
289
isProgressIndicator() const290 bool AccessibilityRenderObject::isProgressIndicator() const
291 {
292 return roleValue() == ProgressIndicatorRole;
293 }
294
isSlider() const295 bool AccessibilityRenderObject::isSlider() const
296 {
297 return roleValue() == SliderRole;
298 }
299
isMenuRelated() const300 bool AccessibilityRenderObject::isMenuRelated() const
301 {
302 AccessibilityRole role = roleValue();
303 return role == MenuRole
304 || role == MenuBarRole
305 || role == MenuButtonRole
306 || role == MenuItemRole;
307 }
308
isMenu() const309 bool AccessibilityRenderObject::isMenu() const
310 {
311 return roleValue() == MenuRole;
312 }
313
isMenuBar() const314 bool AccessibilityRenderObject::isMenuBar() const
315 {
316 return roleValue() == MenuBarRole;
317 }
318
isMenuButton() const319 bool AccessibilityRenderObject::isMenuButton() const
320 {
321 return roleValue() == MenuButtonRole;
322 }
323
isMenuItem() const324 bool AccessibilityRenderObject::isMenuItem() const
325 {
326 return roleValue() == MenuItemRole;
327 }
328
isPressed() const329 bool AccessibilityRenderObject::isPressed() const
330 {
331 ASSERT(m_renderer);
332 if (roleValue() != ButtonRole)
333 return false;
334
335 Node* node = m_renderer->node();
336 if (!node)
337 return false;
338
339 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
340 if (ariaRoleAttribute() == ButtonRole) {
341 if (equalIgnoringCase(getAttribute(aria_pressedAttr).string(), "true"))
342 return true;
343 return false;
344 }
345
346 return node->active();
347 }
348
isIndeterminate() const349 bool AccessibilityRenderObject::isIndeterminate() const
350 {
351 ASSERT(m_renderer);
352 if (!m_renderer->node() || !m_renderer->node()->isElementNode())
353 return false;
354
355 InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
356 if (!inputElement)
357 return false;
358
359 return inputElement->isIndeterminate();
360 }
361
isChecked() const362 bool AccessibilityRenderObject::isChecked() const
363 {
364 ASSERT(m_renderer);
365 if (!m_renderer->node() || !m_renderer->node()->isElementNode())
366 return false;
367
368 // First test for native checkedness semantics
369 InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
370 if (inputElement)
371 return inputElement->isChecked();
372
373 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
374 AccessibilityRole ariaRole = ariaRoleAttribute();
375 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
376 if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
377 return true;
378 return false;
379 }
380
381 // Otherwise it's not checked
382 return false;
383 }
384
isHovered() const385 bool AccessibilityRenderObject::isHovered() const
386 {
387 ASSERT(m_renderer);
388 return m_renderer->node() && m_renderer->node()->hovered();
389 }
390
isMultiSelectable() const391 bool AccessibilityRenderObject::isMultiSelectable() const
392 {
393 ASSERT(m_renderer);
394
395 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
396 if (equalIgnoringCase(ariaMultiSelectable, "true"))
397 return true;
398 if (equalIgnoringCase(ariaMultiSelectable, "false"))
399 return false;
400
401 if (!m_renderer->isListBox())
402 return false;
403 return m_renderer->node() && static_cast<HTMLSelectElement*>(m_renderer->node())->multiple();
404 }
405
isReadOnly() const406 bool AccessibilityRenderObject::isReadOnly() const
407 {
408 ASSERT(m_renderer);
409
410 if (isWebArea()) {
411 Document* document = m_renderer->document();
412 if (!document)
413 return true;
414
415 HTMLElement* body = document->body();
416 if (body && body->isContentEditable())
417 return false;
418
419 Frame* frame = document->frame();
420 if (!frame)
421 return true;
422
423 return !frame->isContentEditable();
424 }
425
426 if (m_renderer->isTextField())
427 return static_cast<HTMLInputElement*>(m_renderer->node())->readOnly();
428 if (m_renderer->isTextArea())
429 return static_cast<HTMLTextAreaElement*>(m_renderer->node())->readOnly();
430
431 return !m_renderer->node() || !m_renderer->node()->isContentEditable();
432 }
433
isOffScreen() const434 bool AccessibilityRenderObject::isOffScreen() const
435 {
436 ASSERT(m_renderer);
437 IntRect contentRect = m_renderer->absoluteClippedOverflowRect();
438 FrameView* view = m_renderer->document()->frame()->view();
439 FloatRect viewRect = view->visibleContentRect();
440 viewRect.intersect(contentRect);
441 return viewRect.isEmpty();
442 }
443
headingLevel() const444 int AccessibilityRenderObject::headingLevel() const
445 {
446 // headings can be in block flow and non-block flow
447 if (!m_renderer)
448 return 0;
449
450 Node* node = m_renderer->node();
451 if (!node)
452 return 0;
453
454 if (ariaRoleAttribute() == HeadingRole) {
455 if (!node->isElementNode())
456 return 0;
457 Element* element = static_cast<Element*>(node);
458 return element->getAttribute(aria_levelAttr).toInt();
459 }
460
461 if (node->hasTagName(h1Tag))
462 return 1;
463
464 if (node->hasTagName(h2Tag))
465 return 2;
466
467 if (node->hasTagName(h3Tag))
468 return 3;
469
470 if (node->hasTagName(h4Tag))
471 return 4;
472
473 if (node->hasTagName(h5Tag))
474 return 5;
475
476 if (node->hasTagName(h6Tag))
477 return 6;
478
479 return 0;
480 }
481
isHeading() const482 bool AccessibilityRenderObject::isHeading() const
483 {
484 return roleValue() == HeadingRole;
485 }
486
isLink() const487 bool AccessibilityRenderObject::isLink() const
488 {
489 return roleValue() == WebCoreLinkRole;
490 }
491
isControl() const492 bool AccessibilityRenderObject::isControl() const
493 {
494 if (!m_renderer)
495 return false;
496
497 Node* node = m_renderer->node();
498 return node && ((node->isElementNode() && static_cast<Element*>(node)->isFormControlElement())
499 || AccessibilityObject::isARIAControl(ariaRoleAttribute()));
500 }
501
isFieldset() const502 bool AccessibilityRenderObject::isFieldset() const
503 {
504 if (!m_renderer)
505 return false;
506
507 return m_renderer->isFieldset();
508 }
509
isGroup() const510 bool AccessibilityRenderObject::isGroup() const
511 {
512 return roleValue() == GroupRole;
513 }
514
selectedRadioButton()515 AccessibilityObject* AccessibilityRenderObject::selectedRadioButton()
516 {
517 if (!isRadioGroup())
518 return 0;
519
520 // Find the child radio button that is selected (ie. the intValue == 1).
521 int count = m_children.size();
522 for (int i = 0; i < count; ++i) {
523 AccessibilityObject* object = m_children[i].get();
524 if (object->roleValue() == RadioButtonRole && object->intValue() == 1)
525 return object;
526 }
527 return 0;
528 }
529
selectedTabItem()530 AccessibilityObject* AccessibilityRenderObject::selectedTabItem()
531 {
532 if (!isTabList())
533 return 0;
534
535 // Find the child tab item that is selected (ie. the intValue == 1).
536 AccessibilityObject::AccessibilityChildrenVector tabs;
537 tabChildren(tabs);
538
539 int count = tabs.size();
540 for (int i = 0; i < count; ++i) {
541 AccessibilityObject* object = m_children[i].get();
542 if (object->isTabItem() && object->intValue() == 1)
543 return object;
544 }
545 return 0;
546 }
547
getAttribute(const QualifiedName & attribute) const548 const AtomicString& AccessibilityRenderObject::getAttribute(const QualifiedName& attribute) const
549 {
550 Node* node = m_renderer->node();
551 if (!node)
552 return nullAtom;
553
554 if (!node->isElementNode())
555 return nullAtom;
556
557 Element* element = static_cast<Element*>(node);
558 return element->getAttribute(attribute);
559 }
560
anchorElement() const561 Element* AccessibilityRenderObject::anchorElement() const
562 {
563 if (!m_renderer)
564 return 0;
565
566 AXObjectCache* cache = axObjectCache();
567 RenderObject* currRenderer;
568
569 // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though.
570 for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) {
571 if (currRenderer->isRenderBlock()) {
572 RenderInline* continuation = toRenderBlock(currRenderer)->inlineContinuation();
573 if (continuation)
574 return cache->getOrCreate(continuation)->anchorElement();
575 }
576 }
577
578 // bail if none found
579 if (!currRenderer)
580 return 0;
581
582 // search up the DOM tree for an anchor element
583 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
584 Node* node = currRenderer->node();
585 for ( ; node; node = node->parentNode()) {
586 if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
587 return static_cast<Element*>(node);
588 }
589
590 return 0;
591 }
592
actionElement() const593 Element* AccessibilityRenderObject::actionElement() const
594 {
595 if (!m_renderer)
596 return 0;
597
598 Node* node = m_renderer->node();
599 if (node) {
600 if (node->hasTagName(inputTag)) {
601 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
602 if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton()))
603 return input;
604 } else if (node->hasTagName(buttonTag))
605 return static_cast<Element*>(node);
606 }
607
608 if (isFileUploadButton())
609 return static_cast<Element*>(m_renderer->node());
610
611 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
612 return static_cast<Element*>(m_renderer->node());
613
614 if (isImageButton())
615 return static_cast<Element*>(m_renderer->node());
616
617 if (m_renderer->isMenuList())
618 return static_cast<Element*>(m_renderer->node());
619
620 AccessibilityRole role = roleValue();
621 if (role == ButtonRole || role == PopUpButtonRole)
622 return static_cast<Element*>(m_renderer->node());
623
624 Element* elt = anchorElement();
625 if (!elt)
626 elt = mouseButtonListener();
627 return elt;
628 }
629
mouseButtonListener() const630 Element* AccessibilityRenderObject::mouseButtonListener() const
631 {
632 Node* node = m_renderer->node();
633 if (!node)
634 return 0;
635
636 // check if our parent is a mouse button listener
637 while (node && !node->isElementNode())
638 node = node->parent();
639
640 if (!node)
641 return 0;
642
643 // FIXME: Do the continuation search like anchorElement does
644 for (Element* element = static_cast<Element*>(node); element; element = element->parentElement()) {
645 if (element->getAttributeEventListener(eventNames().clickEvent) || element->getAttributeEventListener(eventNames().mousedownEvent) || element->getAttributeEventListener(eventNames().mouseupEvent))
646 return element;
647 }
648
649 return 0;
650 }
651
increment()652 void AccessibilityRenderObject::increment()
653 {
654 if (roleValue() != SliderRole)
655 return;
656
657 changeValueByPercent(5);
658 }
659
decrement()660 void AccessibilityRenderObject::decrement()
661 {
662 if (roleValue() != SliderRole)
663 return;
664
665 changeValueByPercent(-5);
666 }
667
siblingWithAriaRole(String role,Node * node)668 static Element* siblingWithAriaRole(String role, Node* node)
669 {
670 Node* sibling = node->parent()->firstChild();
671 while (sibling) {
672 if (sibling->isElementNode()) {
673 String siblingAriaRole = static_cast<Element*>(sibling)->getAttribute(roleAttr).string();
674 if (equalIgnoringCase(siblingAriaRole, role))
675 return static_cast<Element*>(sibling);
676 }
677 sibling = sibling->nextSibling();
678 }
679
680 return 0;
681 }
682
menuElementForMenuButton() const683 Element* AccessibilityRenderObject::menuElementForMenuButton() const
684 {
685 if (ariaRoleAttribute() != MenuButtonRole)
686 return 0;
687
688 return siblingWithAriaRole("menu", renderer()->node());
689 }
690
menuForMenuButton() const691 AccessibilityObject* AccessibilityRenderObject::menuForMenuButton() const
692 {
693 Element* menu = menuElementForMenuButton();
694 if (menu && menu->renderer())
695 return m_renderer->document()->axObjectCache()->getOrCreate(menu->renderer());
696 return 0;
697 }
698
menuItemElementForMenu() const699 Element* AccessibilityRenderObject::menuItemElementForMenu() const
700 {
701 if (ariaRoleAttribute() != MenuRole)
702 return 0;
703
704 return siblingWithAriaRole("menuitem", renderer()->node());
705 }
706
menuButtonForMenu() const707 AccessibilityObject* AccessibilityRenderObject::menuButtonForMenu() const
708 {
709 Element* menuItem = menuItemElementForMenu();
710
711 if (menuItem && menuItem->renderer()) {
712 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
713 AccessibilityObject* menuItemAX = m_renderer->document()->axObjectCache()->getOrCreate(menuItem->renderer());
714 if (menuItemAX->isMenuButton())
715 return menuItemAX;
716 }
717 return 0;
718 }
719
helpText() const720 String AccessibilityRenderObject::helpText() const
721 {
722 if (!m_renderer)
723 return String();
724
725 for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) {
726 if (curr->node() && curr->node()->isHTMLElement()) {
727 const AtomicString& summary = static_cast<Element*>(curr->node())->getAttribute(summaryAttr);
728 if (!summary.isEmpty())
729 return summary;
730 const AtomicString& title = static_cast<Element*>(curr->node())->getAttribute(titleAttr);
731 if (!title.isEmpty())
732 return title;
733 }
734 }
735
736 return String();
737 }
738
hierarchicalLevel() const739 unsigned AccessibilityRenderObject::hierarchicalLevel() const
740 {
741 if (!m_renderer)
742 return 0;
743
744 Node* node = m_renderer->node();
745 if (!node || !node->isElementNode())
746 return 0;
747 Element* element = static_cast<Element*>(node);
748 String ariaLevel = element->getAttribute(aria_levelAttr);
749 if (!ariaLevel.isEmpty())
750 return ariaLevel.toInt();
751
752 // Only tree item will calculate its level through the DOM currently.
753 if (roleValue() != TreeItemRole)
754 return 0;
755
756 // Hierarchy leveling starts at 0.
757 // We measure tree hierarchy by the number of groups that the item is within.
758 unsigned level = 0;
759 AccessibilityObject* parent = parentObject();
760 while (parent) {
761 AccessibilityRole parentRole = parent->roleValue();
762 if (parentRole == GroupRole)
763 level++;
764 else if (parentRole == TreeRole)
765 break;
766
767 parent = parent->parentObject();
768 }
769
770 return level;
771 }
772
language() const773 String AccessibilityRenderObject::language() const
774 {
775 if (!m_renderer)
776 return String();
777
778 // Defer to parent if this element doesn't have a language set
779 Node* node = m_renderer->node();
780 if (!node)
781 return AccessibilityObject::language();
782
783 if (!node->isElementNode())
784 return AccessibilityObject::language();
785
786 String language = static_cast<Element*>(node)->getAttribute(langAttr);
787 if (language.isEmpty())
788 return AccessibilityObject::language();
789 return language;
790 }
791
textUnderElement() const792 String AccessibilityRenderObject::textUnderElement() const
793 {
794 if (!m_renderer)
795 return String();
796
797 if (isFileUploadButton())
798 return toRenderFileUploadControl(m_renderer)->buttonValue();
799
800 Node* node = m_renderer->node();
801 if (node) {
802 if (Frame* frame = node->document()->frame()) {
803 // catch stale WebCoreAXObject (see <rdar://problem/3960196>)
804 if (frame->document() != node->document())
805 return String();
806 return plainText(rangeOfContents(node).get());
807 }
808 }
809
810 // Sometimes text fragments don't have Node's associated with them (like when
811 // CSS content is used to insert text).
812 if (m_renderer->isText()) {
813 RenderText* renderTextObject = toRenderText(m_renderer);
814 if (renderTextObject->isTextFragment())
815 return String(static_cast<RenderTextFragment*>(m_renderer)->contentString());
816 }
817
818 // return the null string for anonymous text because it is non-trivial to get
819 // the actual text and, so far, that is not needed
820 return String();
821 }
822
hasIntValue() const823 bool AccessibilityRenderObject::hasIntValue() const
824 {
825 if (isHeading())
826 return true;
827
828 if (m_renderer->node() && isCheckboxOrRadio())
829 return true;
830
831 return false;
832 }
833
intValue() const834 int AccessibilityRenderObject::intValue() const
835 {
836 if (!m_renderer || isPasswordField())
837 return 0;
838
839 if (isHeading())
840 return headingLevel();
841
842 Node* node = m_renderer->node();
843 if (!node || !isCheckboxOrRadio())
844 return 0;
845
846 // If this is an ARIA checkbox or radio, check the aria-checked attribute rather than node()->checked()
847 AccessibilityRole ariaRole = ariaRoleAttribute();
848 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
849 if (equalIgnoringCase(getAttribute(aria_checkedAttr).string(), "true"))
850 return true;
851 return false;
852 }
853
854 return static_cast<HTMLInputElement*>(node)->checked();
855 }
856
valueDescription() const857 String AccessibilityRenderObject::valueDescription() const
858 {
859 // Only sliders and progress bars support value descriptions currently.
860 if (!isProgressIndicator() && !isSlider())
861 return String();
862
863 return getAttribute(aria_valuetextAttr).string();
864 }
865
valueForRange() const866 float AccessibilityRenderObject::valueForRange() const
867 {
868 if (!isProgressIndicator() && !isSlider() && !isScrollbar())
869 return 0.0f;
870
871 return getAttribute(aria_valuenowAttr).toFloat();
872 }
873
maxValueForRange() const874 float AccessibilityRenderObject::maxValueForRange() const
875 {
876 if (!isProgressIndicator() && !isSlider())
877 return 0.0f;
878
879 return getAttribute(aria_valuemaxAttr).toFloat();
880 }
881
minValueForRange() const882 float AccessibilityRenderObject::minValueForRange() const
883 {
884 if (!isProgressIndicator() && !isSlider())
885 return 0.0f;
886
887 return getAttribute(aria_valueminAttr).toFloat();
888 }
889
stringValue() const890 String AccessibilityRenderObject::stringValue() const
891 {
892 if (!m_renderer || isPasswordField())
893 return String();
894
895 if (ariaRoleAttribute() == StaticTextRole)
896 return text();
897
898 if (m_renderer->isText())
899 return textUnderElement();
900
901 if (m_renderer->isMenuList())
902 return toRenderMenuList(m_renderer)->text();
903
904 if (m_renderer->isListMarker())
905 return toRenderListMarker(m_renderer)->text();
906
907 if (m_renderer->isRenderButton())
908 return toRenderButton(m_renderer)->text();
909
910 if (isWebArea()) {
911 if (m_renderer->document()->frame())
912 return String();
913
914 // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here
915 VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0);
916 VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX);
917 if (startVisiblePosition.isNull() || endVisiblePosition.isNull())
918 return String();
919
920 return plainText(makeRange(startVisiblePosition, endVisiblePosition).get());
921 }
922
923 if (isTextControl())
924 return text();
925
926 if (isFileUploadButton())
927 return toRenderFileUploadControl(m_renderer)->fileTextValue();
928
929 // FIXME: We might need to implement a value here for more types
930 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
931 // this would require subclassing or making accessibilityAttributeNames do something other than return a
932 // single static array.
933 return String();
934 }
935
936 // This function implements the ARIA accessible name as described by the Mozilla
937 // ARIA Implementer's Guide.
accessibleNameForNode(Node * node)938 static String accessibleNameForNode(Node* node)
939 {
940 if (node->isTextNode())
941 return static_cast<Text*>(node)->data();
942
943 if (node->hasTagName(inputTag))
944 return static_cast<HTMLInputElement*>(node)->value();
945
946 if (node->isHTMLElement()) {
947 const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr);
948 if (!alt.isEmpty())
949 return alt;
950 }
951
952 return String();
953 }
954
accessibilityDescriptionForElements(Vector<Element * > & elements) const955 String AccessibilityRenderObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
956 {
957 Vector<UChar> ariaLabel;
958 unsigned size = elements.size();
959 for (unsigned i = 0; i < size; ++i) {
960 Element* idElement = elements[i];
961
962 String nameFragment = accessibleNameForNode(idElement);
963 ariaLabel.append(nameFragment.characters(), nameFragment.length());
964 for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement)) {
965 nameFragment = accessibleNameForNode(n);
966 ariaLabel.append(nameFragment.characters(), nameFragment.length());
967 }
968
969 if (i != size - 1)
970 ariaLabel.append(' ');
971 }
972 return String::adopt(ariaLabel);
973 }
974
975
elementsFromAttribute(Vector<Element * > & elements,const QualifiedName & attribute) const976 void AccessibilityRenderObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
977 {
978 Node* node = m_renderer->node();
979 if (!node || !node->isElementNode())
980 return;
981
982 Document* document = m_renderer->document();
983 if (!document)
984 return;
985
986 String idList = getAttribute(attribute).string();
987 if (idList.isEmpty())
988 return;
989
990 idList.replace('\n', ' ');
991 Vector<String> idVector;
992 idList.split(' ', idVector);
993
994 unsigned size = idVector.size();
995 for (unsigned i = 0; i < size; ++i) {
996 String idName = idVector[i];
997 Element* idElement = document->getElementById(idName);
998 if (idElement)
999 elements.append(idElement);
1000 }
1001 }
1002
ariaLabeledByElements(Vector<Element * > & elements) const1003 void AccessibilityRenderObject::ariaLabeledByElements(Vector<Element*>& elements) const
1004 {
1005 elementsFromAttribute(elements, aria_labeledbyAttr);
1006 if (!elements.size())
1007 elementsFromAttribute(elements, aria_labelledbyAttr);
1008 }
1009
ariaLabeledByAttribute() const1010 String AccessibilityRenderObject::ariaLabeledByAttribute() const
1011 {
1012 Vector<Element*> elements;
1013 ariaLabeledByElements(elements);
1014
1015 return accessibilityDescriptionForElements(elements);
1016 }
1017
labelForElement(Element * element)1018 static HTMLLabelElement* labelForElement(Element* element)
1019 {
1020 RefPtr<NodeList> list = element->document()->getElementsByTagName("label");
1021 unsigned len = list->length();
1022 for (unsigned i = 0; i < len; i++) {
1023 if (list->item(i)->hasTagName(labelTag)) {
1024 HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i));
1025 if (label->correspondingControl() == element)
1026 return label;
1027 }
1028 }
1029
1030 return 0;
1031 }
1032
labelElementContainer() const1033 HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const
1034 {
1035 if (!m_renderer)
1036 return false;
1037
1038 // the control element should not be considered part of the label
1039 if (isControl())
1040 return false;
1041
1042 // find if this has a parent that is a label
1043 for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) {
1044 if (parentNode->hasTagName(labelTag))
1045 return static_cast<HTMLLabelElement*>(parentNode);
1046 }
1047
1048 return 0;
1049 }
1050
title() const1051 String AccessibilityRenderObject::title() const
1052 {
1053 AccessibilityRole ariaRole = ariaRoleAttribute();
1054
1055 if (!m_renderer)
1056 return String();
1057
1058 Node* node = m_renderer->node();
1059 if (!node)
1060 return String();
1061
1062 String ariaLabel = ariaLabeledByAttribute();
1063 if (!ariaLabel.isEmpty())
1064 return ariaLabel;
1065
1066 const AtomicString& title = getAttribute(titleAttr);
1067 if (!title.isEmpty())
1068 return title;
1069
1070 bool isInputTag = node->hasTagName(inputTag);
1071 if (isInputTag) {
1072 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
1073 if (input->isTextButton())
1074 return input->value();
1075 }
1076
1077 if (isInputTag || AccessibilityObject::isARIAInput(ariaRole) || isControl()) {
1078 HTMLLabelElement* label = labelForElement(static_cast<Element*>(node));
1079 if (label && !titleUIElement())
1080 return label->innerText();
1081
1082 const AtomicString& placeholder = getAttribute(placeholderAttr);
1083 if (!placeholder.isEmpty())
1084 return placeholder;
1085 }
1086
1087 if (roleValue() == ButtonRole
1088 || ariaRole == ListBoxOptionRole
1089 || ariaRole == MenuItemRole
1090 || ariaRole == MenuButtonRole
1091 || ariaRole == RadioButtonRole
1092 || ariaRole == CheckBoxRole
1093 || ariaRole == TabRole
1094 || isHeading())
1095 return textUnderElement();
1096
1097 if (isLink())
1098 return textUnderElement();
1099
1100 return String();
1101 }
1102
ariaDescribedByAttribute() const1103 String AccessibilityRenderObject::ariaDescribedByAttribute() const
1104 {
1105 Vector<Element*> elements;
1106 elementsFromAttribute(elements, aria_describedbyAttr);
1107
1108 return accessibilityDescriptionForElements(elements);
1109 }
1110
accessibilityDescription() const1111 String AccessibilityRenderObject::accessibilityDescription() const
1112 {
1113 if (!m_renderer)
1114 return String();
1115
1116 String ariaLabel = getAttribute(aria_labelAttr).string();
1117 if (!ariaLabel.isEmpty())
1118 return ariaLabel;
1119
1120 String ariaDescription = ariaDescribedByAttribute();
1121 if (!ariaDescription.isEmpty())
1122 return ariaDescription;
1123
1124 if (isImage() || isInputImage() || isNativeImage()) {
1125 Node* node = m_renderer->node();
1126 if (node && node->isHTMLElement()) {
1127 const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr);
1128 if (alt.isEmpty())
1129 return String();
1130 return alt;
1131 }
1132 }
1133
1134 if (isWebArea()) {
1135 Document* document = m_renderer->document();
1136 Node* owner = document->ownerElement();
1137 if (owner) {
1138 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1139 const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr);
1140 if (!title.isEmpty())
1141 return title;
1142 return static_cast<HTMLFrameElementBase*>(owner)->getAttribute(nameAttr);
1143 }
1144 if (owner->isHTMLElement())
1145 return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr);
1146 }
1147 owner = document->body();
1148 if (owner && owner->isHTMLElement())
1149 return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr);
1150 }
1151
1152 return String();
1153 }
1154
boundingBoxRect() const1155 IntRect AccessibilityRenderObject::boundingBoxRect() const
1156 {
1157 RenderObject* obj = m_renderer;
1158
1159 if (!obj)
1160 return IntRect();
1161
1162 if (obj->node()) // If we are a continuation, we want to make sure to use the primary renderer.
1163 obj = obj->node()->renderer();
1164
1165 Vector<FloatQuad> quads;
1166 if (obj->isText())
1167 obj->absoluteQuads(quads);
1168 else
1169 obj->absoluteFocusRingQuads(quads);
1170 const size_t n = quads.size();
1171 if (!n)
1172 return IntRect();
1173
1174 IntRect result;
1175 for (size_t i = 0; i < n; ++i) {
1176 IntRect r = quads[i].enclosingBoundingBox();
1177 if (!r.isEmpty()) {
1178 if (obj->style()->hasAppearance())
1179 obj->theme()->adjustRepaintRect(obj, r);
1180 result.unite(r);
1181 }
1182 }
1183 return result;
1184 }
1185
checkboxOrRadioRect() const1186 IntRect AccessibilityRenderObject::checkboxOrRadioRect() const
1187 {
1188 if (!m_renderer)
1189 return IntRect();
1190
1191 HTMLLabelElement* label = labelForElement(static_cast<Element*>(m_renderer->node()));
1192 if (!label || !label->renderer())
1193 return boundingBoxRect();
1194
1195 IntRect labelRect = axObjectCache()->getOrCreate(label->renderer())->elementRect();
1196 labelRect.unite(boundingBoxRect());
1197 return labelRect;
1198 }
1199
elementRect() const1200 IntRect AccessibilityRenderObject::elementRect() const
1201 {
1202 // a checkbox or radio button should encompass its label
1203 if (isCheckboxOrRadio())
1204 return checkboxOrRadioRect();
1205
1206 return boundingBoxRect();
1207 }
1208
size() const1209 IntSize AccessibilityRenderObject::size() const
1210 {
1211 IntRect rect = elementRect();
1212 return rect.size();
1213 }
1214
clickPoint() const1215 IntPoint AccessibilityRenderObject::clickPoint() const
1216 {
1217 // use the default position unless this is an editable web area, in which case we use the selection bounds.
1218 if (!isWebArea() || isReadOnly())
1219 return AccessibilityObject::clickPoint();
1220
1221 VisibleSelection visSelection = selection();
1222 VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd());
1223 IntRect bounds = boundsForVisiblePositionRange(range);
1224 #if PLATFORM(MAC)
1225 bounds.setLocation(m_renderer->document()->view()->screenToContents(bounds.location()));
1226 #endif
1227 return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2));
1228 }
1229
internalLinkElement() const1230 AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const
1231 {
1232 Element* element = anchorElement();
1233 if (!element)
1234 return 0;
1235
1236 // Right now, we do not support ARIA links as internal link elements
1237 if (!element->hasTagName(aTag))
1238 return 0;
1239 HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(element);
1240
1241 KURL linkURL = anchor->href();
1242 String fragmentIdentifier = linkURL.fragmentIdentifier();
1243 if (fragmentIdentifier.isEmpty())
1244 return 0;
1245
1246 // check if URL is the same as current URL
1247 linkURL.removeFragmentIdentifier();
1248 if (m_renderer->document()->url() != linkURL)
1249 return 0;
1250
1251 Node* linkedNode = m_renderer->document()->findAnchor(fragmentIdentifier);
1252 if (!linkedNode)
1253 return 0;
1254
1255 // The element we find may not be accessible, so find the first accessible object.
1256 return firstAccessibleObjectFromNode(linkedNode);
1257 }
1258
addRadioButtonGroupMembers(AccessibilityChildrenVector & linkedUIElements) const1259 void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const
1260 {
1261 if (!m_renderer || roleValue() != RadioButtonRole)
1262 return;
1263
1264 Node* node = m_renderer->node();
1265 if (!node || !node->hasTagName(inputTag))
1266 return;
1267
1268 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
1269 // if there's a form, then this is easy
1270 if (input->form()) {
1271 Vector<RefPtr<Node> > formElements;
1272 input->form()->getNamedElements(input->name(), formElements);
1273
1274 unsigned len = formElements.size();
1275 for (unsigned i = 0; i < len; ++i) {
1276 Node* associateElement = formElements[i].get();
1277 if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->getOrCreate(associateElement->renderer()))
1278 linkedUIElements.append(object);
1279 }
1280 } else {
1281 RefPtr<NodeList> list = node->document()->getElementsByTagName("input");
1282 unsigned len = list->length();
1283 for (unsigned i = 0; i < len; ++i) {
1284 if (list->item(i)->hasTagName(inputTag)) {
1285 HTMLInputElement* associateElement = static_cast<HTMLInputElement*>(list->item(i));
1286 if (associateElement->isRadioButton() && associateElement->name() == input->name()) {
1287 if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->getOrCreate(associateElement->renderer()))
1288 linkedUIElements.append(object);
1289 }
1290 }
1291 }
1292 }
1293 }
1294
1295 // linked ui elements could be all the related radio buttons in a group
1296 // or an internal anchor connection
linkedUIElements(AccessibilityChildrenVector & linkedUIElements) const1297 void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const
1298 {
1299 ariaFlowToElements(linkedUIElements);
1300
1301 if (isAnchor()) {
1302 AccessibilityObject* linkedAXElement = internalLinkElement();
1303 if (linkedAXElement)
1304 linkedUIElements.append(linkedAXElement);
1305 }
1306
1307 if (roleValue() == RadioButtonRole)
1308 addRadioButtonGroupMembers(linkedUIElements);
1309 }
1310
hasTextAlternative() const1311 bool AccessibilityRenderObject::hasTextAlternative() const
1312 {
1313 // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should
1314 // override the "label" element association.
1315 if (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).string().isEmpty())
1316 return true;
1317
1318 return false;
1319 }
1320
supportsARIAFlowTo() const1321 bool AccessibilityRenderObject::supportsARIAFlowTo() const
1322 {
1323 return !getAttribute(aria_flowtoAttr).string().isEmpty();
1324 }
1325
ariaFlowToElements(AccessibilityChildrenVector & flowTo) const1326 void AccessibilityRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const
1327 {
1328 Vector<Element*> elements;
1329 elementsFromAttribute(elements, aria_flowtoAttr);
1330
1331 AXObjectCache* cache = axObjectCache();
1332 unsigned count = elements.size();
1333 for (unsigned k = 0; k < count; ++k) {
1334 Element* element = elements[k];
1335 AccessibilityObject* flowToElement = cache->getOrCreate(element->renderer());
1336 if (flowToElement)
1337 flowTo.append(flowToElement);
1338 }
1339
1340 }
1341
supportsARIADropping()1342 bool AccessibilityRenderObject::supportsARIADropping()
1343 {
1344 const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr).string();
1345 return !dropEffect.isEmpty();
1346 }
1347
supportsARIADragging()1348 bool AccessibilityRenderObject::supportsARIADragging()
1349 {
1350 const AtomicString& grabbed = getAttribute(aria_grabbedAttr).string();
1351 return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "false");
1352 }
1353
isARIAGrabbed()1354 bool AccessibilityRenderObject::isARIAGrabbed()
1355 {
1356 return elementAttributeValue(aria_grabbedAttr);
1357 }
1358
setARIAGrabbed(bool grabbed)1359 void AccessibilityRenderObject::setARIAGrabbed(bool grabbed)
1360 {
1361 setElementAttributeValue(aria_grabbedAttr, grabbed);
1362 }
1363
determineARIADropEffects(Vector<String> & effects)1364 void AccessibilityRenderObject::determineARIADropEffects(Vector<String>& effects)
1365 {
1366 String dropEffects = getAttribute(aria_dropeffectAttr).string();
1367 if (dropEffects.isEmpty()) {
1368 effects.clear();
1369 return;
1370 }
1371
1372 dropEffects.replace('\n', ' ');
1373 dropEffects.split(' ', effects);
1374 }
1375
exposesTitleUIElement() const1376 bool AccessibilityRenderObject::exposesTitleUIElement() const
1377 {
1378 if (!isControl())
1379 return false;
1380
1381 // checkbox or radio buttons don't expose the title ui element unless it has a title already
1382 if (isCheckboxOrRadio() && getAttribute(titleAttr).isEmpty())
1383 return false;
1384
1385 if (hasTextAlternative())
1386 return false;
1387
1388 return true;
1389 }
1390
titleUIElement() const1391 AccessibilityObject* AccessibilityRenderObject::titleUIElement() const
1392 {
1393 if (!m_renderer)
1394 return 0;
1395
1396 // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset
1397 if (isFieldset())
1398 return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend());
1399
1400 if (!exposesTitleUIElement())
1401 return 0;
1402
1403 Node* element = m_renderer->node();
1404 HTMLLabelElement* label = labelForElement(static_cast<Element*>(element));
1405 if (label && label->renderer())
1406 return axObjectCache()->getOrCreate(label->renderer());
1407
1408 return 0;
1409 }
1410
ariaIsHidden() const1411 bool AccessibilityRenderObject::ariaIsHidden() const
1412 {
1413 if (equalIgnoringCase(getAttribute(aria_hiddenAttr).string(), "true"))
1414 return true;
1415
1416 // aria-hidden hides this object and any children
1417 AccessibilityObject* object = parentObject();
1418 while (object) {
1419 if (object->isAccessibilityRenderObject() && equalIgnoringCase(static_cast<AccessibilityRenderObject*>(object)->getAttribute(aria_hiddenAttr).string(), "true"))
1420 return true;
1421 object = object->parentObject();
1422 }
1423
1424 return false;
1425 }
1426
isDescendantOfBarrenParent() const1427 bool AccessibilityRenderObject::isDescendantOfBarrenParent() const
1428 {
1429 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
1430 if (!object->canHaveChildren())
1431 return true;
1432 }
1433
1434 return false;
1435 }
1436
isAllowedChildOfTree() const1437 bool AccessibilityRenderObject::isAllowedChildOfTree() const
1438 {
1439 // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline.
1440 AccessibilityObject* axObj = parentObject();
1441 bool isInTree = false;
1442 while (axObj) {
1443 if (axObj->isTree()) {
1444 isInTree = true;
1445 break;
1446 }
1447 axObj = axObj->parentObject();
1448 }
1449
1450 // If the object is in a tree, only tree items should be exposed (and the children of tree items).
1451 if (isInTree) {
1452 AccessibilityRole role = roleValue();
1453 if (role != TreeItemRole && role != StaticTextRole)
1454 return false;
1455 }
1456 return true;
1457 }
1458
accessibilityIsIgnored() const1459 bool AccessibilityRenderObject::accessibilityIsIgnored() const
1460 {
1461 // Is the platform interested in this object?
1462 AccessibilityObjectPlatformInclusion decision = accessibilityPlatformIncludesObject();
1463 if (decision == IncludeObject)
1464 return false;
1465 if (decision == IgnoreObject)
1466 return true;
1467 // the decision must, therefore, be DefaultBehavior.
1468
1469 // ignore invisible element
1470 if (!m_renderer || m_renderer->style()->visibility() != VISIBLE)
1471 return true;
1472
1473 if (ariaIsHidden())
1474 return true;
1475
1476 if (isPresentationalChildOfAriaRole())
1477 return true;
1478
1479 // If this element is within a parent that cannot have children, it should not be exposed.
1480 if (isDescendantOfBarrenParent())
1481 return true;
1482
1483 if (roleValue() == IgnoredRole)
1484 return true;
1485
1486 // An ARIA tree can only have tree items and static text as children.
1487 if (!isAllowedChildOfTree())
1488 return true;
1489
1490 // ignore popup menu items because AppKit does
1491 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) {
1492 if (parent->isMenuList())
1493 return true;
1494 }
1495
1496 // find out if this element is inside of a label element.
1497 // if so, it may be ignored because it's the label for a checkbox or radio button
1498 AccessibilityObject* controlObject = correspondingControlForLabelElement();
1499 if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio())
1500 return true;
1501
1502 AccessibilityRole ariaRole = ariaRoleAttribute();
1503 if (ariaRole == TextAreaRole || ariaRole == StaticTextRole) {
1504 String ariaText = text();
1505 return ariaText.isNull() || ariaText.isEmpty();
1506 }
1507
1508 // NOTE: BRs always have text boxes now, so the text box check here can be removed
1509 if (m_renderer->isText()) {
1510 // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level
1511 if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole
1512 || parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole)
1513 return true;
1514 RenderText* renderText = toRenderText(m_renderer);
1515 if (m_renderer->isBR() || !renderText->firstTextBox())
1516 return true;
1517
1518 // text elements that are just empty whitespace should not be returned
1519 return renderText->text()->containsOnlyWhitespace();
1520 }
1521
1522 if (isHeading())
1523 return false;
1524
1525 if (isLink())
1526 return false;
1527
1528 // all controls are accessible
1529 if (isControl())
1530 return false;
1531
1532 if (ariaRole != UnknownRole)
1533 return false;
1534
1535 // don't ignore labels, because they serve as TitleUIElements
1536 Node* node = m_renderer->node();
1537 if (node && node->hasTagName(labelTag))
1538 return false;
1539
1540 // Anything that is content editable should not be ignored.
1541 // However, one cannot just call node->isContentEditable() since that will ask if its parents
1542 // are also editable. Only the top level content editable region should be exposed.
1543 if (node && node->isElementNode()) {
1544 Element* element = static_cast<Element*>(node);
1545 const AtomicString& contentEditable = element->getAttribute(contenteditableAttr);
1546 if (equalIgnoringCase(contentEditable, "true"))
1547 return false;
1548 }
1549
1550 if (m_renderer->isBlockFlow() && m_renderer->childrenInline())
1551 return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener();
1552
1553 // ignore images seemingly used as spacers
1554 if (isImage()) {
1555 if (node && node->isElementNode()) {
1556 Element* elt = static_cast<Element*>(node);
1557 const AtomicString& alt = elt->getAttribute(altAttr);
1558 // don't ignore an image that has an alt tag
1559 if (!alt.isEmpty())
1560 return false;
1561 // informal standard is to ignore images with zero-length alt strings
1562 if (!alt.isNull())
1563 return true;
1564 }
1565
1566 if (node && node->hasTagName(canvasTag)) {
1567 RenderHTMLCanvas* canvas = toRenderHTMLCanvas(m_renderer);
1568 if (canvas->height() <= 1 || canvas->width() <= 1)
1569 return true;
1570 return false;
1571 }
1572
1573 if (isNativeImage()) {
1574 // check for one-dimensional image
1575 RenderImage* image = toRenderImage(m_renderer);
1576 if (image->height() <= 1 || image->width() <= 1)
1577 return true;
1578
1579 // check whether rendered image was stretched from one-dimensional file image
1580 if (image->cachedImage()) {
1581 IntSize imageSize = image->cachedImage()->imageSize(image->view()->zoomFactor());
1582 return imageSize.height() <= 1 || imageSize.width() <= 1;
1583 }
1584 }
1585 return false;
1586 }
1587
1588 // make a platform-specific decision
1589 if (isAttachment())
1590 return accessibilityIgnoreAttachment();
1591
1592 return !m_renderer->isListMarker() && !isWebArea();
1593 }
1594
isLoaded() const1595 bool AccessibilityRenderObject::isLoaded() const
1596 {
1597 return !m_renderer->document()->tokenizer();
1598 }
1599
estimatedLoadingProgress() const1600 double AccessibilityRenderObject::estimatedLoadingProgress() const
1601 {
1602 if (!m_renderer)
1603 return 0;
1604
1605 if (isLoaded())
1606 return 1.0;
1607
1608 Page* page = m_renderer->document()->page();
1609 if (!page)
1610 return 0;
1611
1612 return page->progress()->estimatedProgress();
1613 }
1614
layoutCount() const1615 int AccessibilityRenderObject::layoutCount() const
1616 {
1617 if (!m_renderer->isRenderView())
1618 return 0;
1619 return toRenderView(m_renderer)->frameView()->layoutCount();
1620 }
1621
text() const1622 String AccessibilityRenderObject::text() const
1623 {
1624 // If this is a user defined static text, use the accessible name computation.
1625 if (ariaRoleAttribute() == StaticTextRole)
1626 return accessibilityDescription();
1627
1628 if (!isTextControl() || isPasswordField())
1629 return String();
1630
1631 if (isNativeTextControl())
1632 return toRenderTextControl(m_renderer)->text();
1633
1634 Node* node = m_renderer->node();
1635 if (!node)
1636 return String();
1637 if (!node->isElementNode())
1638 return String();
1639
1640 return static_cast<Element*>(node)->innerText();
1641 }
1642
textLength() const1643 int AccessibilityRenderObject::textLength() const
1644 {
1645 ASSERT(isTextControl());
1646
1647 if (isPasswordField())
1648 return -1; // need to return something distinct from 0
1649
1650 return text().length();
1651 }
1652
ariaSelectedTextDOMRange() const1653 PassRefPtr<Range> AccessibilityRenderObject::ariaSelectedTextDOMRange() const
1654 {
1655 Node* node = m_renderer->node();
1656 if (!node)
1657 return 0;
1658
1659 RefPtr<Range> currentSelectionRange = selection().toNormalizedRange();
1660 if (!currentSelectionRange)
1661 return 0;
1662
1663 ExceptionCode ec = 0;
1664 if (!currentSelectionRange->intersectsNode(node, ec))
1665 return Range::create(currentSelectionRange->ownerDocument());
1666
1667 RefPtr<Range> ariaRange = rangeOfContents(node);
1668 Position startPosition, endPosition;
1669
1670 // Find intersection of currentSelectionRange and ariaRange
1671 if (ariaRange->startOffset() > currentSelectionRange->startOffset())
1672 startPosition = ariaRange->startPosition();
1673 else
1674 startPosition = currentSelectionRange->startPosition();
1675
1676 if (ariaRange->endOffset() < currentSelectionRange->endOffset())
1677 endPosition = ariaRange->endPosition();
1678 else
1679 endPosition = currentSelectionRange->endPosition();
1680
1681 return Range::create(ariaRange->ownerDocument(), startPosition, endPosition);
1682 }
1683
selectedText() const1684 String AccessibilityRenderObject::selectedText() const
1685 {
1686 ASSERT(isTextControl());
1687
1688 if (isPasswordField())
1689 return String(); // need to return something distinct from empty string
1690
1691 if (isNativeTextControl()) {
1692 RenderTextControl* textControl = toRenderTextControl(m_renderer);
1693 return textControl->text().substring(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart());
1694 }
1695
1696 if (ariaRoleAttribute() == UnknownRole)
1697 return String();
1698
1699 RefPtr<Range> ariaRange = ariaSelectedTextDOMRange();
1700 if (!ariaRange)
1701 return String();
1702 return ariaRange->text();
1703 }
1704
accessKey() const1705 const AtomicString& AccessibilityRenderObject::accessKey() const
1706 {
1707 Node* node = m_renderer->node();
1708 if (!node)
1709 return nullAtom;
1710 if (!node->isElementNode())
1711 return nullAtom;
1712 return static_cast<Element*>(node)->getAttribute(accesskeyAttr);
1713 }
1714
selection() const1715 VisibleSelection AccessibilityRenderObject::selection() const
1716 {
1717 return m_renderer->document()->frame()->selection()->selection();
1718 }
1719
selectedTextRange() const1720 PlainTextRange AccessibilityRenderObject::selectedTextRange() const
1721 {
1722 ASSERT(isTextControl());
1723
1724 if (isPasswordField())
1725 return PlainTextRange();
1726
1727 AccessibilityRole ariaRole = ariaRoleAttribute();
1728 if (isNativeTextControl() && ariaRole == UnknownRole) {
1729 RenderTextControl* textControl = toRenderTextControl(m_renderer);
1730 return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart());
1731 }
1732
1733 if (ariaRole == UnknownRole)
1734 return PlainTextRange();
1735
1736 RefPtr<Range> ariaRange = ariaSelectedTextDOMRange();
1737 if (!ariaRange)
1738 return PlainTextRange();
1739 return PlainTextRange(ariaRange->startOffset(), ariaRange->endOffset());
1740 }
1741
setSelectedTextRange(const PlainTextRange & range)1742 void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range)
1743 {
1744 if (isNativeTextControl()) {
1745 RenderTextControl* textControl = toRenderTextControl(m_renderer);
1746 textControl->setSelectionRange(range.start, range.start + range.length);
1747 return;
1748 }
1749
1750 Document* document = m_renderer->document();
1751 if (!document)
1752 return;
1753 Frame* frame = document->frame();
1754 if (!frame)
1755 return;
1756 Node* node = m_renderer->node();
1757 frame->selection()->setSelection(VisibleSelection(Position(node, range.start),
1758 Position(node, range.start + range.length), DOWNSTREAM));
1759 }
1760
url() const1761 KURL AccessibilityRenderObject::url() const
1762 {
1763 if (isAnchor() && m_renderer->node()->hasTagName(aTag)) {
1764 if (HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(anchorElement()))
1765 return anchor->href();
1766 }
1767
1768 if (isWebArea())
1769 return m_renderer->document()->url();
1770
1771 if (isImage() && m_renderer->node() && m_renderer->node()->hasTagName(imgTag))
1772 return static_cast<HTMLImageElement*>(m_renderer->node())->src();
1773
1774 if (isInputImage())
1775 return static_cast<HTMLInputElement*>(m_renderer->node())->src();
1776
1777 return KURL();
1778 }
1779
isVisited() const1780 bool AccessibilityRenderObject::isVisited() const
1781 {
1782 return m_renderer->style()->pseudoState() == PseudoVisited;
1783 }
1784
isExpanded() const1785 bool AccessibilityRenderObject::isExpanded() const
1786 {
1787 if (equalIgnoringCase(getAttribute(aria_expandedAttr).string(), "true"))
1788 return true;
1789
1790 return false;
1791 }
1792
setElementAttributeValue(const QualifiedName & attributeName,bool value)1793 void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& attributeName, bool value)
1794 {
1795 if (!m_renderer)
1796 return;
1797
1798 Node* node = m_renderer->node();
1799 if (!node || !node->isElementNode())
1800 return;
1801
1802 Element* element = static_cast<Element*>(node);
1803 element->setAttribute(attributeName, (value) ? "true" : "false");
1804 }
1805
elementAttributeValue(const QualifiedName & attributeName) const1806 bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const
1807 {
1808 if (!m_renderer)
1809 return false;
1810
1811 return equalIgnoringCase(getAttribute(attributeName), "true");
1812 }
1813
setIsExpanded(bool isExpanded)1814 void AccessibilityRenderObject::setIsExpanded(bool isExpanded)
1815 {
1816 // Combo boxes, tree items and rows can be expanded (in different ways on different platforms).
1817 // That action translates into setting the aria-expanded attribute to true.
1818 AccessibilityRole role = roleValue();
1819 switch (role) {
1820 case ComboBoxRole:
1821 case TreeItemRole:
1822 case RowRole:
1823 setElementAttributeValue(aria_expandedAttr, isExpanded);
1824 break;
1825 default:
1826 break;
1827 }
1828 }
1829
isRequired() const1830 bool AccessibilityRenderObject::isRequired() const
1831 {
1832 if (equalIgnoringCase(getAttribute(aria_requiredAttr).string(), "true"))
1833 return true;
1834
1835 return false;
1836 }
1837
isSelected() const1838 bool AccessibilityRenderObject::isSelected() const
1839 {
1840 if (!m_renderer)
1841 return false;
1842
1843 Node* node = m_renderer->node();
1844 if (!node)
1845 return false;
1846
1847 String ariaSelected = getAttribute(aria_selectedAttr).string();
1848 if (equalIgnoringCase(ariaSelected, "true"))
1849 return true;
1850
1851 if (isTabItem() && isTabItemSelected())
1852 return true;
1853
1854 return false;
1855 }
1856
isTabItemSelected() const1857 bool AccessibilityRenderObject::isTabItemSelected() const
1858 {
1859 if (!isTabItem() || !m_renderer)
1860 return false;
1861
1862 Node* node = m_renderer->node();
1863 if (!node || !node->isElementNode())
1864 return false;
1865
1866 // The ARIA spec says a tab item can also be selected if it is aria-labeled by a tabpanel
1867 // that has keyboard focus inside of it, or if a tabpanel in its aria-controls list has KB
1868 // focus inside of it.
1869 AccessibilityObject* focusedElement = focusedUIElement();
1870 if (!focusedElement)
1871 return false;
1872
1873 Vector<Element*> elements;
1874 elementsFromAttribute(elements, aria_controlsAttr);
1875
1876 unsigned count = elements.size();
1877 for (unsigned k = 0; k < count; ++k) {
1878 Element* element = elements[k];
1879 AccessibilityObject* tabPanel = axObjectCache()->getOrCreate(element->renderer());
1880
1881 // A tab item should only control tab panels.
1882 if (!tabPanel || tabPanel->roleValue() != TabPanelRole)
1883 continue;
1884
1885 AccessibilityObject* checkFocusElement = focusedElement;
1886 // Check if the focused element is a descendant of the element controlled by the tab item.
1887 while (checkFocusElement) {
1888 if (tabPanel == checkFocusElement)
1889 return true;
1890 checkFocusElement = checkFocusElement->parentObject();
1891 }
1892 }
1893
1894 return false;
1895 }
1896
isFocused() const1897 bool AccessibilityRenderObject::isFocused() const
1898 {
1899 if (!m_renderer)
1900 return false;
1901
1902 Document* document = m_renderer->document();
1903 if (!document)
1904 return false;
1905
1906 Node* focusedNode = document->focusedNode();
1907 if (!focusedNode)
1908 return false;
1909
1910 // A web area is represented by the Document node in the DOM tree, which isn't focusable.
1911 // Check instead if the frame's selection controller is focused
1912 if (focusedNode == m_renderer->node()
1913 || (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive()))
1914 return true;
1915
1916 return false;
1917 }
1918
setFocused(bool on)1919 void AccessibilityRenderObject::setFocused(bool on)
1920 {
1921 if (!canSetFocusAttribute())
1922 return;
1923
1924 if (!on)
1925 m_renderer->document()->setFocusedNode(0);
1926 else {
1927 if (m_renderer->node()->isElementNode())
1928 static_cast<Element*>(m_renderer->node())->focus();
1929 else
1930 m_renderer->document()->setFocusedNode(m_renderer->node());
1931 }
1932 }
1933
changeValueByPercent(float percentChange)1934 void AccessibilityRenderObject::changeValueByPercent(float percentChange)
1935 {
1936 float range = maxValueForRange() - minValueForRange();
1937 float value = valueForRange();
1938
1939 value += range * (percentChange / 100);
1940 setValue(String::number(value));
1941
1942 axObjectCache()->postNotification(m_renderer, AXObjectCache::AXValueChanged, true);
1943 }
1944
setSelected(bool enabled)1945 void AccessibilityRenderObject::setSelected(bool enabled)
1946 {
1947 setElementAttributeValue(aria_selectedAttr, enabled);
1948 }
1949
setSelectedRows(AccessibilityChildrenVector & selectedRows)1950 void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows)
1951 {
1952 // Setting selected only makes sense in trees and tables (and tree-tables).
1953 AccessibilityRole role = roleValue();
1954 if (role != TreeRole && role != TreeGridRole && role != TableRole)
1955 return;
1956
1957 bool isMulti = isMultiSelectable();
1958 unsigned count = selectedRows.size();
1959 if (count > 1 && !isMulti)
1960 count = 1;
1961
1962 for (unsigned k = 0; k < count; ++k)
1963 selectedRows[k]->setSelected(true);
1964 }
1965
setValue(const String & string)1966 void AccessibilityRenderObject::setValue(const String& string)
1967 {
1968 if (!m_renderer)
1969 return;
1970
1971 // FIXME: Do we want to do anything here for ARIA textboxes?
1972 if (m_renderer->isTextField()) {
1973 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node());
1974 input->setValue(string);
1975 } else if (m_renderer->isTextArea()) {
1976 HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(m_renderer->node());
1977 textArea->setValue(string);
1978 } else if (roleValue() == SliderRole) {
1979 Node* element = m_renderer->node();
1980 if (element && element->isElementNode())
1981 static_cast<Element*>(element)->setAttribute(aria_valuenowAttr, string);
1982 }
1983 }
1984
ariaOwnsElements(AccessibilityChildrenVector & axObjects) const1985 void AccessibilityRenderObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const
1986 {
1987 Vector<Element*> elements;
1988 elementsFromAttribute(elements, aria_ownsAttr);
1989
1990 unsigned count = elements.size();
1991 for (unsigned k = 0; k < count; ++k) {
1992 RenderObject* render = elements[k]->renderer();
1993 AccessibilityObject* obj = axObjectCache()->getOrCreate(render);
1994 if (obj)
1995 axObjects.append(obj);
1996 }
1997 }
1998
supportsARIAOwns() const1999 bool AccessibilityRenderObject::supportsARIAOwns() const
2000 {
2001 if (!m_renderer)
2002 return false;
2003 const AtomicString& ariaOwns = getAttribute(aria_ownsAttr).string();
2004
2005 return !ariaOwns.isEmpty();
2006 }
2007
isEnabled() const2008 bool AccessibilityRenderObject::isEnabled() const
2009 {
2010 ASSERT(m_renderer);
2011
2012 if (equalIgnoringCase(getAttribute(aria_disabledAttr).string(), "true"))
2013 return false;
2014
2015 Node* node = m_renderer->node();
2016 if (!node || !node->isElementNode())
2017 return true;
2018
2019 return static_cast<Element*>(node)->isEnabledFormControl();
2020 }
2021
topRenderer() const2022 RenderView* AccessibilityRenderObject::topRenderer() const
2023 {
2024 return m_renderer->document()->topDocument()->renderView();
2025 }
2026
document() const2027 Document* AccessibilityRenderObject::document() const
2028 {
2029 if (!m_renderer)
2030 return 0;
2031 return m_renderer->document();
2032 }
2033
topDocumentFrameView() const2034 FrameView* AccessibilityRenderObject::topDocumentFrameView() const
2035 {
2036 return topRenderer()->view()->frameView();
2037 }
2038
widget() const2039 Widget* AccessibilityRenderObject::widget() const
2040 {
2041 if (!m_renderer->isWidget())
2042 return 0;
2043 return toRenderWidget(m_renderer)->widget();
2044 }
2045
axObjectCache() const2046 AXObjectCache* AccessibilityRenderObject::axObjectCache() const
2047 {
2048 return m_renderer->document()->axObjectCache();
2049 }
2050
accessibilityParentForImageMap(HTMLMapElement * map) const2051 AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const
2052 {
2053 // find an image that is using this map
2054 if (!map)
2055 return 0;
2056
2057 HTMLImageElement* imageElement = map->imageElement();
2058 if (!imageElement)
2059 return 0;
2060
2061 return axObjectCache()->getOrCreate(imageElement->renderer());
2062 }
2063
getDocumentLinks(AccessibilityChildrenVector & result)2064 void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result)
2065 {
2066 Document* document = m_renderer->document();
2067 RefPtr<HTMLCollection> coll = document->links();
2068 Node* curr = coll->firstItem();
2069 while (curr) {
2070 RenderObject* obj = curr->renderer();
2071 if (obj) {
2072 RefPtr<AccessibilityObject> axobj = document->axObjectCache()->getOrCreate(obj);
2073 ASSERT(axobj);
2074 if (!axobj->accessibilityIsIgnored() && axobj->isLink())
2075 result.append(axobj);
2076 } else {
2077 Node* parent = curr->parent();
2078 if (parent && curr->hasTagName(areaTag) && parent->hasTagName(mapTag)) {
2079 AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole));
2080 areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(curr));
2081 areaObject->setHTMLMapElement(static_cast<HTMLMapElement*>(parent));
2082 areaObject->setParent(accessibilityParentForImageMap(static_cast<HTMLMapElement*>(parent)));
2083
2084 result.append(areaObject);
2085 }
2086 }
2087 curr = coll->nextItem();
2088 }
2089 }
2090
documentFrameView() const2091 FrameView* AccessibilityRenderObject::documentFrameView() const
2092 {
2093 if (!m_renderer || !m_renderer->document())
2094 return 0;
2095
2096 // this is the RenderObject's Document's Frame's FrameView
2097 return m_renderer->document()->view();
2098 }
2099
widgetForAttachmentView() const2100 Widget* AccessibilityRenderObject::widgetForAttachmentView() const
2101 {
2102 if (!isAttachment())
2103 return 0;
2104 return toRenderWidget(m_renderer)->widget();
2105 }
2106
frameViewIfRenderView() const2107 FrameView* AccessibilityRenderObject::frameViewIfRenderView() const
2108 {
2109 if (!m_renderer->isRenderView())
2110 return 0;
2111 // this is the RenderObject's Document's renderer's FrameView
2112 return m_renderer->view()->frameView();
2113 }
2114
2115 // This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns
2116 // a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file
visiblePositionRange() const2117 VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const
2118 {
2119 if (!m_renderer)
2120 return VisiblePositionRange();
2121
2122 // construct VisiblePositions for start and end
2123 Node* node = m_renderer->node();
2124 if (!node)
2125 return VisiblePositionRange();
2126
2127 VisiblePosition startPos = firstDeepEditingPositionForNode(node);
2128 VisiblePosition endPos = lastDeepEditingPositionForNode(node);
2129
2130 // the VisiblePositions are equal for nodes like buttons, so adjust for that
2131 // FIXME: Really? [button, 0] and [button, 1] are distinct (before and after the button)
2132 // I expect this code is only hit for things like empty divs? In which case I don't think
2133 // the behavior is correct here -- eseidel
2134 if (startPos == endPos) {
2135 endPos = endPos.next();
2136 if (endPos.isNull())
2137 endPos = startPos;
2138 }
2139
2140 return VisiblePositionRange(startPos, endPos);
2141 }
2142
visiblePositionRangeForLine(unsigned lineCount) const2143 VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const
2144 {
2145 if (!lineCount || !m_renderer)
2146 return VisiblePositionRange();
2147
2148 // iterate over the lines
2149 // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the
2150 // last offset of the last line
2151 VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForCoordinates(0, 0);
2152 VisiblePosition savedVisiblePos;
2153 while (--lineCount) {
2154 savedVisiblePos = visiblePos;
2155 visiblePos = nextLinePosition(visiblePos, 0);
2156 if (visiblePos.isNull() || visiblePos == savedVisiblePos)
2157 return VisiblePositionRange();
2158 }
2159
2160 // make a caret selection for the marker position, then extend it to the line
2161 // NOTE: ignores results of sel.modify because it returns false when
2162 // starting at an empty line. The resulting selection in that case
2163 // will be a caret at visiblePos.
2164 SelectionController selection;
2165 selection.setSelection(VisibleSelection(visiblePos));
2166 selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary);
2167
2168 return VisiblePositionRange(selection.selection().visibleStart(), selection.selection().visibleEnd());
2169 }
2170
visiblePositionForIndex(int index) const2171 VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const
2172 {
2173 if (!m_renderer)
2174 return VisiblePosition();
2175
2176 if (isNativeTextControl())
2177 return toRenderTextControl(m_renderer)->visiblePositionForIndex(index);
2178
2179 if (!isTextControl() && !m_renderer->isText())
2180 return VisiblePosition();
2181
2182 Node* node = m_renderer->node();
2183 if (!node)
2184 return VisiblePosition();
2185
2186 if (index <= 0)
2187 return VisiblePosition(node, 0, DOWNSTREAM);
2188
2189 ExceptionCode ec = 0;
2190 RefPtr<Range> range = Range::create(m_renderer->document());
2191 range->selectNodeContents(node, ec);
2192 CharacterIterator it(range.get());
2193 it.advance(index - 1);
2194 return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM);
2195 }
2196
indexForVisiblePosition(const VisiblePosition & pos) const2197 int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const
2198 {
2199 if (isNativeTextControl())
2200 return toRenderTextControl(m_renderer)->indexForVisiblePosition(pos);
2201
2202 if (!isTextControl())
2203 return 0;
2204
2205 Node* node = m_renderer->node();
2206 if (!node)
2207 return 0;
2208
2209 Position indexPosition = pos.deepEquivalent();
2210 if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != node)
2211 return 0;
2212
2213 ExceptionCode ec = 0;
2214 RefPtr<Range> range = Range::create(m_renderer->document());
2215 range->setStart(node, 0, ec);
2216 range->setEnd(indexPosition.node(), indexPosition.deprecatedEditingOffset(), ec);
2217 return TextIterator::rangeLength(range.get());
2218 }
2219
boundsForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const2220 IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
2221 {
2222 if (visiblePositionRange.isNull())
2223 return IntRect();
2224
2225 // Create a mutable VisiblePositionRange.
2226 VisiblePositionRange range(visiblePositionRange);
2227 IntRect rect1 = range.start.absoluteCaretBounds();
2228 IntRect rect2 = range.end.absoluteCaretBounds();
2229
2230 // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds
2231 if (rect2.y() != rect1.y()) {
2232 VisiblePosition endOfFirstLine = endOfLine(range.start);
2233 if (range.start == endOfFirstLine) {
2234 range.start.setAffinity(DOWNSTREAM);
2235 rect1 = range.start.absoluteCaretBounds();
2236 }
2237 if (range.end == endOfFirstLine) {
2238 range.end.setAffinity(UPSTREAM);
2239 rect2 = range.end.absoluteCaretBounds();
2240 }
2241 }
2242
2243 IntRect ourrect = rect1;
2244 ourrect.unite(rect2);
2245
2246 // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead
2247 if (rect1.bottom() != rect2.bottom()) {
2248 RefPtr<Range> dataRange = makeRange(range.start, range.end);
2249 IntRect boundingBox = dataRange->boundingBox();
2250 String rangeString = plainText(dataRange.get());
2251 if (rangeString.length() > 1 && !boundingBox.isEmpty())
2252 ourrect = boundingBox;
2253 }
2254
2255 #if PLATFORM(MAC)
2256 return m_renderer->document()->view()->contentsToScreen(ourrect);
2257 #else
2258 return ourrect;
2259 #endif
2260 }
2261
setSelectedVisiblePositionRange(const VisiblePositionRange & range) const2262 void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const
2263 {
2264 if (range.start.isNull() || range.end.isNull())
2265 return;
2266
2267 // make selection and tell the document to use it. if it's zero length, then move to that position
2268 if (range.start == range.end)
2269 m_renderer->document()->frame()->selection()->moveTo(range.start, true);
2270 else {
2271 VisibleSelection newSelection = VisibleSelection(range.start, range.end);
2272 m_renderer->document()->frame()->selection()->setSelection(newSelection);
2273 }
2274 }
2275
visiblePositionForPoint(const IntPoint & point) const2276 VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const
2277 {
2278 // convert absolute point to view coordinates
2279 FrameView* frameView = m_renderer->document()->topDocument()->renderer()->view()->frameView();
2280 RenderView* renderView = topRenderer();
2281 Node* innerNode = 0;
2282
2283 // locate the node containing the point
2284 IntPoint pointResult;
2285 while (1) {
2286 IntPoint ourpoint;
2287 #if PLATFORM(MAC)
2288 ourpoint = frameView->screenToContents(point);
2289 #else
2290 ourpoint = point;
2291 #endif
2292 HitTestRequest request(HitTestRequest::ReadOnly |
2293 HitTestRequest::Active);
2294 HitTestResult result(ourpoint);
2295 renderView->layer()->hitTest(request, result);
2296 innerNode = result.innerNode();
2297 if (!innerNode || !innerNode->renderer())
2298 return VisiblePosition();
2299
2300 pointResult = result.localPoint();
2301
2302 // done if hit something other than a widget
2303 RenderObject* renderer = innerNode->renderer();
2304 if (!renderer->isWidget())
2305 break;
2306
2307 // descend into widget (FRAME, IFRAME, OBJECT...)
2308 Widget* widget = toRenderWidget(renderer)->widget();
2309 if (!widget || !widget->isFrameView())
2310 break;
2311 Frame* frame = static_cast<FrameView*>(widget)->frame();
2312 if (!frame)
2313 break;
2314 renderView = frame->document()->renderView();
2315 frameView = static_cast<FrameView*>(widget);
2316 }
2317
2318 return innerNode->renderer()->positionForPoint(pointResult);
2319 }
2320
2321 // NOTE: Consider providing this utility method as AX API
visiblePositionForIndex(unsigned indexValue,bool lastIndexOK) const2322 VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const
2323 {
2324 if (!isTextControl())
2325 return VisiblePosition();
2326
2327 // lastIndexOK specifies whether the position after the last character is acceptable
2328 if (indexValue >= text().length()) {
2329 if (!lastIndexOK || indexValue > text().length())
2330 return VisiblePosition();
2331 }
2332 VisiblePosition position = visiblePositionForIndex(indexValue);
2333 position.setAffinity(DOWNSTREAM);
2334 return position;
2335 }
2336
2337 // NOTE: Consider providing this utility method as AX API
index(const VisiblePosition & position) const2338 int AccessibilityRenderObject::index(const VisiblePosition& position) const
2339 {
2340 if (!isTextControl())
2341 return -1;
2342
2343 Node* node = position.deepEquivalent().node();
2344 if (!node)
2345 return -1;
2346
2347 for (RenderObject* renderer = node->renderer(); renderer && renderer->node(); renderer = renderer->parent()) {
2348 if (renderer == m_renderer)
2349 return indexForVisiblePosition(position);
2350 }
2351
2352 return -1;
2353 }
2354
2355 // Given a line number, the range of characters of the text associated with this accessibility
2356 // object that contains the line number.
doAXRangeForLine(unsigned lineNumber) const2357 PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const
2358 {
2359 if (!isTextControl())
2360 return PlainTextRange();
2361
2362 // iterate to the specified line
2363 VisiblePosition visiblePos = visiblePositionForIndex(0);
2364 VisiblePosition savedVisiblePos;
2365 for (unsigned lineCount = lineNumber; lineCount; lineCount -= 1) {
2366 savedVisiblePos = visiblePos;
2367 visiblePos = nextLinePosition(visiblePos, 0);
2368 if (visiblePos.isNull() || visiblePos == savedVisiblePos)
2369 return PlainTextRange();
2370 }
2371
2372 // make a caret selection for the marker position, then extend it to the line
2373 // NOTE: ignores results of selection.modify because it returns false when
2374 // starting at an empty line. The resulting selection in that case
2375 // will be a caret at visiblePos.
2376 SelectionController selection;
2377 selection.setSelection(VisibleSelection(visiblePos));
2378 selection.modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary);
2379 selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary);
2380
2381 // calculate the indices for the selection start and end
2382 VisiblePosition startPosition = selection.selection().visibleStart();
2383 VisiblePosition endPosition = selection.selection().visibleEnd();
2384 int index1 = indexForVisiblePosition(startPosition);
2385 int index2 = indexForVisiblePosition(endPosition);
2386
2387 // add one to the end index for a line break not caused by soft line wrap (to match AppKit)
2388 if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull())
2389 index2 += 1;
2390
2391 // return nil rather than an zero-length range (to match AppKit)
2392 if (index1 == index2)
2393 return PlainTextRange();
2394
2395 return PlainTextRange(index1, index2 - index1);
2396 }
2397
2398 // The composed character range in the text associated with this accessibility object that
2399 // is specified by the given index value. This parameterized attribute returns the complete
2400 // range of characters (including surrogate pairs of multi-byte glyphs) at the given index.
doAXRangeForIndex(unsigned index) const2401 PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const
2402 {
2403 if (!isTextControl())
2404 return PlainTextRange();
2405
2406 String elementText = text();
2407 if (!elementText.length() || index > elementText.length() - 1)
2408 return PlainTextRange();
2409
2410 return PlainTextRange(index, 1);
2411 }
2412
2413 // A substring of the text associated with this accessibility object that is
2414 // specified by the given character range.
doAXStringForRange(const PlainTextRange & range) const2415 String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const
2416 {
2417 if (isPasswordField())
2418 return String();
2419
2420 if (!range.length)
2421 return String();
2422
2423 if (!isTextControl())
2424 return String();
2425
2426 String elementText = text();
2427 if (range.start + range.length > elementText.length())
2428 return String();
2429
2430 return elementText.substring(range.start, range.length);
2431 }
2432
2433 // The bounding rectangle of the text associated with this accessibility object that is
2434 // specified by the given range. This is the bounding rectangle a sighted user would see
2435 // on the display screen, in pixels.
doAXBoundsForRange(const PlainTextRange & range) const2436 IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const
2437 {
2438 if (isTextControl())
2439 return boundsForVisiblePositionRange(visiblePositionRangeForRange(range));
2440 return IntRect();
2441 }
2442
accessibilityImageMapHitTest(HTMLAreaElement * area,const IntPoint & point) const2443 AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const
2444 {
2445 if (!area)
2446 return 0;
2447
2448 HTMLMapElement* map = static_cast<HTMLMapElement*>(area->parent());
2449 AccessibilityObject* parent = accessibilityParentForImageMap(map);
2450 if (!parent)
2451 return 0;
2452
2453 AccessibilityObject::AccessibilityChildrenVector children = parent->children();
2454
2455 unsigned count = children.size();
2456 for (unsigned k = 0; k < count; ++k) {
2457 if (children[k]->elementRect().contains(point))
2458 return children[k].get();
2459 }
2460
2461 return 0;
2462 }
2463
doAccessibilityHitTest(const IntPoint & point) const2464 AccessibilityObject* AccessibilityRenderObject::doAccessibilityHitTest(const IntPoint& point) const
2465 {
2466 if (!m_renderer || !m_renderer->hasLayer())
2467 return 0;
2468
2469 RenderLayer* layer = toRenderBox(m_renderer)->layer();
2470
2471 HitTestRequest request(HitTestRequest::ReadOnly |
2472 HitTestRequest::Active);
2473 HitTestResult hitTestResult = HitTestResult(point);
2474 layer->hitTest(request, hitTestResult);
2475 if (!hitTestResult.innerNode())
2476 return 0;
2477 Node* node = hitTestResult.innerNode()->shadowAncestorNode();
2478
2479 if (node->hasTagName(areaTag))
2480 return accessibilityImageMapHitTest(static_cast<HTMLAreaElement*>(node), point);
2481
2482 RenderObject* obj = node->renderer();
2483 if (!obj)
2484 return 0;
2485
2486 AccessibilityObject* result = obj->document()->axObjectCache()->getOrCreate(obj);
2487
2488 if (obj->isListBox())
2489 return static_cast<AccessibilityListBox*>(result)->doAccessibilityHitTest(point);
2490
2491 if (result->accessibilityIsIgnored()) {
2492 // If this element is the label of a control, a hit test should return the control.
2493 AccessibilityObject* controlObject = result->correspondingControlForLabelElement();
2494 if (controlObject && !controlObject->exposesTitleUIElement())
2495 return controlObject;
2496
2497 result = result->parentObjectUnignored();
2498 }
2499
2500 return result;
2501 }
2502
focusedUIElement() const2503 AccessibilityObject* AccessibilityRenderObject::focusedUIElement() const
2504 {
2505 Page* page = m_renderer->document()->page();
2506 if (!page)
2507 return 0;
2508
2509 return AXObjectCache::focusedUIElementForPage(page);
2510 }
2511
shouldFocusActiveDescendant() const2512 bool AccessibilityRenderObject::shouldFocusActiveDescendant() const
2513 {
2514 switch (ariaRoleAttribute()) {
2515 case GroupRole:
2516 case ComboBoxRole:
2517 case ListBoxRole:
2518 case MenuRole:
2519 case MenuBarRole:
2520 case RadioGroupRole:
2521 case RowRole:
2522 case PopUpButtonRole:
2523 case ProgressIndicatorRole:
2524 case ToolbarRole:
2525 case OutlineRole:
2526 case TreeRole:
2527 case GridRole:
2528 /* FIXME: replace these with actual roles when they are added to AccessibilityRole
2529 composite
2530 alert
2531 alertdialog
2532 status
2533 timer
2534 */
2535 return true;
2536 default:
2537 return false;
2538 }
2539 }
2540
activeDescendant() const2541 AccessibilityObject* AccessibilityRenderObject::activeDescendant() const
2542 {
2543 if (!m_renderer)
2544 return 0;
2545
2546 if (m_renderer->node() && !m_renderer->node()->isElementNode())
2547 return 0;
2548 Element* element = static_cast<Element*>(m_renderer->node());
2549
2550 String activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr).string();
2551 if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty())
2552 return 0;
2553
2554 Element* target = document()->getElementById(activeDescendantAttrStr);
2555 if (!target)
2556 return 0;
2557
2558 AccessibilityObject* obj = axObjectCache()->getOrCreate(target->renderer());
2559 if (obj && obj->isAccessibilityRenderObject())
2560 // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification
2561 return obj;
2562 return 0;
2563 }
2564
2565
handleActiveDescendantChanged()2566 void AccessibilityRenderObject::handleActiveDescendantChanged()
2567 {
2568 Element* element = static_cast<Element*>(renderer()->node());
2569 if (!element)
2570 return;
2571 Document* doc = renderer()->document();
2572 if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedNode() != element)
2573 return;
2574 AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant());
2575
2576 if (activedescendant && shouldFocusActiveDescendant())
2577 doc->axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged, true);
2578 }
2579
correspondingControlForLabelElement() const2580 AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElement() const
2581 {
2582 HTMLLabelElement* labelElement = labelElementContainer();
2583 if (!labelElement)
2584 return 0;
2585
2586 HTMLElement* correspondingControl = labelElement->correspondingControl();
2587 if (!correspondingControl)
2588 return 0;
2589
2590 return axObjectCache()->getOrCreate(correspondingControl->renderer());
2591 }
2592
correspondingLabelForControlElement() const2593 AccessibilityObject* AccessibilityRenderObject::correspondingLabelForControlElement() const
2594 {
2595 if (!m_renderer)
2596 return 0;
2597
2598 Node* node = m_renderer->node();
2599 if (node && node->isHTMLElement()) {
2600 HTMLLabelElement* label = labelForElement(static_cast<Element*>(node));
2601 if (label)
2602 return axObjectCache()->getOrCreate(label->renderer());
2603 }
2604
2605 return 0;
2606 }
2607
observableObject() const2608 AccessibilityObject* AccessibilityRenderObject::observableObject() const
2609 {
2610 for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) {
2611 if (renderer->isTextControl())
2612 return renderer->document()->axObjectCache()->getOrCreate(renderer);
2613 }
2614
2615 return 0;
2616 }
2617
determineAriaRoleAttribute() const2618 AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const
2619 {
2620 String ariaRole = getAttribute(roleAttr).string();
2621 if (ariaRole.isNull() || ariaRole.isEmpty())
2622 return UnknownRole;
2623
2624 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
2625
2626 if (role == ButtonRole && elementAttributeValue(aria_haspopupAttr))
2627 role = PopUpButtonRole;
2628
2629 if (role)
2630 return role;
2631 // selects and listboxes both have options as child roles, but they map to different roles within WebCore
2632 if (equalIgnoringCase(ariaRole, "option")) {
2633 if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole)
2634 return MenuItemRole;
2635 if (parentObjectUnignored()->ariaRoleAttribute() == ListBoxRole)
2636 return ListBoxOptionRole;
2637 }
2638 // an aria "menuitem" may map to MenuButton or MenuItem depending on its parent
2639 if (equalIgnoringCase(ariaRole, "menuitem")) {
2640 if (parentObjectUnignored()->ariaRoleAttribute() == GroupRole)
2641 return MenuButtonRole;
2642 if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole)
2643 return MenuItemRole;
2644 }
2645
2646 return UnknownRole;
2647 }
2648
ariaRoleAttribute() const2649 AccessibilityRole AccessibilityRenderObject::ariaRoleAttribute() const
2650 {
2651 return m_ariaRole;
2652 }
2653
updateAccessibilityRole()2654 void AccessibilityRenderObject::updateAccessibilityRole()
2655 {
2656 m_role = determineAccessibilityRole();
2657 }
2658
determineAccessibilityRole()2659 AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole()
2660 {
2661 if (!m_renderer)
2662 return UnknownRole;
2663
2664 m_ariaRole = determineAriaRoleAttribute();
2665
2666 Node* node = m_renderer->node();
2667 AccessibilityRole ariaRole = ariaRoleAttribute();
2668 if (ariaRole != UnknownRole)
2669 return ariaRole;
2670
2671 if (node && node->isLink()) {
2672 if (m_renderer->isImage())
2673 return ImageMapRole;
2674 return WebCoreLinkRole;
2675 }
2676 if (m_renderer->isListMarker())
2677 return ListMarkerRole;
2678 if (node && node->hasTagName(buttonTag))
2679 return ButtonRole;
2680 if (m_renderer->isText())
2681 return StaticTextRole;
2682 if (m_renderer->isImage()) {
2683 if (node && node->hasTagName(inputTag))
2684 return ButtonRole;
2685 return ImageRole;
2686 }
2687 if (node && node->hasTagName(canvasTag))
2688 return ImageRole;
2689
2690 if (m_renderer->isRenderView())
2691 return WebAreaRole;
2692
2693 if (m_renderer->isTextField())
2694 return TextFieldRole;
2695
2696 if (m_renderer->isTextArea())
2697 return TextAreaRole;
2698
2699 if (node && node->hasTagName(inputTag)) {
2700 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
2701 if (input->inputType() == HTMLInputElement::CHECKBOX)
2702 return CheckBoxRole;
2703 if (input->inputType() == HTMLInputElement::RADIO)
2704 return RadioButtonRole;
2705 if (input->isTextButton())
2706 return ButtonRole;
2707 }
2708
2709 if (node && node->hasTagName(buttonTag))
2710 return ButtonRole;
2711
2712 if (isFileUploadButton())
2713 return ButtonRole;
2714
2715 if (m_renderer->isMenuList())
2716 return PopUpButtonRole;
2717
2718 if (headingLevel())
2719 return HeadingRole;
2720
2721 if (node && node->hasTagName(ddTag))
2722 return DefinitionListDefinitionRole;
2723
2724 if (node && node->hasTagName(dtTag))
2725 return DefinitionListTermRole;
2726
2727 if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag)))
2728 return AnnotationRole;
2729
2730 if (m_renderer->isBlockFlow() || (node && node->hasTagName(labelTag)))
2731 return GroupRole;
2732
2733 return UnknownRole;
2734 }
2735
orientation() const2736 AccessibilityOrientation AccessibilityRenderObject::orientation() const
2737 {
2738 const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr).string();
2739 if (equalIgnoringCase(ariaOrientation, "horizontal"))
2740 return AccessibilityOrientationHorizontal;
2741 if (equalIgnoringCase(ariaOrientation, "vertical"))
2742 return AccessibilityOrientationVertical;
2743
2744 return AccessibilityObject::orientation();
2745 }
2746
isPresentationalChildOfAriaRole() const2747 bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const
2748 {
2749 // Walk the parent chain looking for a parent that has presentational children
2750 AccessibilityObject* parent;
2751 for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject())
2752 { }
2753
2754 return parent;
2755 }
2756
ariaRoleHasPresentationalChildren() const2757 bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const
2758 {
2759 switch (m_ariaRole) {
2760 case ButtonRole:
2761 case SliderRole:
2762 case ImageRole:
2763 case ProgressIndicatorRole:
2764 //case SeparatorRole:
2765 return true;
2766 default:
2767 return false;
2768 }
2769 }
2770
canSetFocusAttribute() const2771 bool AccessibilityRenderObject::canSetFocusAttribute() const
2772 {
2773 ASSERT(m_renderer);
2774 Node* node = m_renderer->node();
2775
2776 // NOTE: It would be more accurate to ask the document whether setFocusedNode() would
2777 // do anything. For example, setFocusedNode() will do nothing if the current focused
2778 // node will not relinquish the focus.
2779 if (!node || !node->isElementNode())
2780 return false;
2781
2782 if (!static_cast<Element*>(node)->isEnabledFormControl())
2783 return false;
2784
2785 switch (roleValue()) {
2786 case WebCoreLinkRole:
2787 case ImageMapLinkRole:
2788 case TextFieldRole:
2789 case TextAreaRole:
2790 case ButtonRole:
2791 case PopUpButtonRole:
2792 case CheckBoxRole:
2793 case RadioButtonRole:
2794 case SliderRole:
2795 return true;
2796 default:
2797 return false;
2798 }
2799 }
2800
canSetExpandedAttribute() const2801 bool AccessibilityRenderObject::canSetExpandedAttribute() const
2802 {
2803 // An object can be expanded if it aria-expanded is true or false.
2804 String ariaExpanded = getAttribute(aria_expandedAttr).string();
2805 return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false");
2806 }
2807
canSetValueAttribute() const2808 bool AccessibilityRenderObject::canSetValueAttribute() const
2809 {
2810 if (equalIgnoringCase(getAttribute(aria_readonlyAttr).string(), "true"))
2811 return false;
2812
2813 // Any node could be contenteditable, so isReadOnly should be relied upon
2814 // for this information for all elements.
2815 return isProgressIndicator() || isSlider() || !isReadOnly();
2816 }
2817
canSetTextRangeAttributes() const2818 bool AccessibilityRenderObject::canSetTextRangeAttributes() const
2819 {
2820 return isTextControl();
2821 }
2822
contentChanged()2823 void AccessibilityRenderObject::contentChanged()
2824 {
2825 // If this element supports ARIA live regions, then notify the AT of changes.
2826 for (RenderObject* renderParent = m_renderer->parent(); renderParent; renderParent = renderParent->parent()) {
2827 AccessibilityObject* parent = m_renderer->document()->axObjectCache()->get(renderParent);
2828 if (!parent)
2829 continue;
2830
2831 // If we find a parent that has ARIA live region on, send the notification and stop processing.
2832 // The spec does not talk about nested live regions.
2833 if (parent->supportsARIALiveRegion()) {
2834 axObjectCache()->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true);
2835 break;
2836 }
2837 }
2838 }
2839
childrenChanged()2840 void AccessibilityRenderObject::childrenChanged()
2841 {
2842 // this method is meant as a quick way of marking dirty
2843 // a portion of the accessibility tree
2844
2845 if (!m_renderer)
2846 return;
2847
2848 // Go up the render parent chain, marking children as dirty.
2849 // We can't rely on the accessibilityParent() because it may not exist and we must not create an AX object here either
2850 // At the same time, process ARIA live region changes.
2851 for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) {
2852 AccessibilityObject* parent = m_renderer->document()->axObjectCache()->get(renderParent);
2853 if (!parent || !parent->isAccessibilityRenderObject())
2854 continue;
2855
2856 AccessibilityRenderObject* axParent = static_cast<AccessibilityRenderObject*>(parent);
2857 // Only do work if the children haven't been marked dirty. This has the effect of blocking
2858 // future live region change notifications until the AX tree has been accessed again. This
2859 // is a good performance win for all parties.
2860 if (!axParent->needsToUpdateChildren()) {
2861 axParent->setNeedsToUpdateChildren();
2862
2863 // If this element supports ARIA live regions, then notify the AT of changes.
2864 if (axParent->supportsARIALiveRegion())
2865 axObjectCache()->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true);
2866 }
2867 }
2868 }
2869
canHaveChildren() const2870 bool AccessibilityRenderObject::canHaveChildren() const
2871 {
2872 if (!m_renderer)
2873 return false;
2874
2875 // Elements that should not have children
2876 switch (roleValue()) {
2877 case ImageRole:
2878 case ButtonRole:
2879 case PopUpButtonRole:
2880 case CheckBoxRole:
2881 case RadioButtonRole:
2882 case TabRole:
2883 case StaticTextRole:
2884 case ListBoxOptionRole:
2885 case ScrollBarRole:
2886 return false;
2887 default:
2888 return true;
2889 }
2890 }
2891
children()2892 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityRenderObject::children()
2893 {
2894 if (m_childrenDirty) {
2895 clearChildren();
2896 m_childrenDirty = false;
2897 }
2898
2899 if (!m_haveChildren)
2900 addChildren();
2901 return m_children;
2902 }
2903
addChildren()2904 void AccessibilityRenderObject::addChildren()
2905 {
2906 // If the need to add more children in addition to existing children arises,
2907 // childrenChanged should have been called, leaving the object with no children.
2908 ASSERT(!m_haveChildren);
2909
2910 // nothing to add if there is no RenderObject
2911 if (!m_renderer)
2912 return;
2913
2914 m_haveChildren = true;
2915
2916 if (!canHaveChildren())
2917 return;
2918
2919 // add all unignored acc children
2920 for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling()) {
2921 if (obj->accessibilityIsIgnored()) {
2922 if (!obj->hasChildren())
2923 obj->addChildren();
2924 AccessibilityChildrenVector children = obj->children();
2925 unsigned length = children.size();
2926 for (unsigned i = 0; i < length; ++i)
2927 m_children.append(children[i]);
2928 } else
2929 m_children.append(obj);
2930 }
2931
2932 // for a RenderImage, add the <area> elements as individual accessibility objects
2933 if (m_renderer->isRenderImage()) {
2934 HTMLMapElement* map = toRenderImage(m_renderer)->imageMap();
2935 if (map) {
2936 for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) {
2937
2938 // add an <area> element for this child if it has a link
2939 if (current->hasTagName(areaTag) && current->isLink()) {
2940 AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(m_renderer->document()->axObjectCache()->getOrCreate(ImageMapLinkRole));
2941 areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(current));
2942 areaObject->setHTMLMapElement(map);
2943 areaObject->setParent(this);
2944
2945 m_children.append(areaObject);
2946 }
2947 }
2948 }
2949 }
2950 }
2951
ariaLiveRegionStatus() const2952 const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const
2953 {
2954 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive"));
2955 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite"));
2956 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off"));
2957
2958 const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr);
2959 // These roles have implicit live region status.
2960 if (liveRegionStatus.isEmpty()) {
2961 switch (roleValue()) {
2962 case ApplicationAlertDialogRole:
2963 case ApplicationAlertRole:
2964 return liveRegionStatusAssertive;
2965 case ApplicationLogRole:
2966 case ApplicationStatusRole:
2967 return liveRegionStatusPolite;
2968 case ApplicationTimerRole:
2969 return liveRegionStatusOff;
2970 default:
2971 break;
2972 }
2973 }
2974
2975 return liveRegionStatus;
2976 }
2977
ariaLiveRegionRelevant() const2978 const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const
2979 {
2980 DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text"));
2981 const AtomicString& relevant = getAttribute(aria_relevantAttr);
2982
2983 // Default aria-relevant = "additions text".
2984 if (relevant.isEmpty())
2985 return defaultLiveRegionRelevant;
2986
2987 return relevant;
2988 }
2989
ariaLiveRegionAtomic() const2990 bool AccessibilityRenderObject::ariaLiveRegionAtomic() const
2991 {
2992 return elementAttributeValue(aria_atomicAttr);
2993 }
2994
ariaLiveRegionBusy() const2995 bool AccessibilityRenderObject::ariaLiveRegionBusy() const
2996 {
2997 return elementAttributeValue(aria_busyAttr);
2998 }
2999
ariaSelectedRows(AccessibilityChildrenVector & result)3000 void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result)
3001 {
3002 // Get all the rows.
3003 AccessibilityChildrenVector allRows;
3004 ariaTreeRows(allRows);
3005
3006 // Determine which rows are selected.
3007 bool isMulti = isMultiSelectable();
3008
3009 // Prefer active descendant over aria-selected.
3010 AccessibilityObject* activeDesc = activeDescendant();
3011 if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) {
3012 result.append(activeDesc);
3013 if (!isMulti)
3014 return;
3015 }
3016
3017 unsigned count = allRows.size();
3018 for (unsigned k = 0; k < count; ++k) {
3019 if (allRows[k]->isSelected()) {
3020 result.append(allRows[k]);
3021 if (!isMulti)
3022 break;
3023 }
3024 }
3025 }
3026
ariaListboxSelectedChildren(AccessibilityChildrenVector & result)3027 void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result)
3028 {
3029 AccessibilityObject* child = firstChild();
3030
3031 Element* element = static_cast<Element*>(renderer()->node());
3032 if (!element || !element->isElementNode()) // do this check to ensure safety of static_cast above
3033 return;
3034
3035 bool isMulti = isMultiSelectable();
3036
3037 while (child) {
3038 // every child should have aria-role option, and if so, check for selected attribute/state
3039 AccessibilityRole ariaRole = child->ariaRoleAttribute();
3040 RenderObject* childRenderer = 0;
3041 if (child->isAccessibilityRenderObject())
3042 childRenderer = static_cast<AccessibilityRenderObject*>(child)->renderer();
3043 if (childRenderer && ariaRole == ListBoxOptionRole) {
3044 Element* childElement = static_cast<Element*>(childRenderer->node());
3045 if (childElement && childElement->isElementNode()) { // do this check to ensure safety of static_cast above
3046 String selectedAttrString = childElement->getAttribute(aria_selectedAttr).string();
3047 if (equalIgnoringCase(selectedAttrString, "true")) {
3048 result.append(child);
3049 if (isMulti)
3050 return;
3051 }
3052 }
3053 }
3054 child = child->nextSibling();
3055 }
3056 }
3057
selectedChildren(AccessibilityChildrenVector & result)3058 void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result)
3059 {
3060 ASSERT(result.isEmpty());
3061
3062 // only listboxes should be asked for their selected children.
3063 AccessibilityRole role = roleValue();
3064 if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes
3065 ariaListboxSelectedChildren(result);
3066 else if (role == TreeRole || role == TreeGridRole || role == TableRole)
3067 ariaSelectedRows(result);
3068 }
3069
ariaListboxVisibleChildren(AccessibilityChildrenVector & result)3070 void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result)
3071 {
3072 if (!hasChildren())
3073 addChildren();
3074
3075 unsigned length = m_children.size();
3076 for (unsigned i = 0; i < length; i++) {
3077 if (!m_children[i]->isOffScreen())
3078 result.append(m_children[i]);
3079 }
3080 }
3081
visibleChildren(AccessibilityChildrenVector & result)3082 void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result)
3083 {
3084 ASSERT(result.isEmpty());
3085
3086 // only listboxes are asked for their visible children.
3087 if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes
3088 ASSERT_NOT_REACHED();
3089 return;
3090 }
3091 return ariaListboxVisibleChildren(result);
3092 }
3093
tabChildren(AccessibilityChildrenVector & result)3094 void AccessibilityRenderObject::tabChildren(AccessibilityChildrenVector& result)
3095 {
3096 ASSERT(roleValue() == TabListRole);
3097
3098 unsigned length = m_children.size();
3099 for (unsigned i = 0; i < length; ++i) {
3100 if (m_children[i]->isTabItem())
3101 result.append(m_children[i]);
3102 }
3103 }
3104
actionVerb() const3105 const String& AccessibilityRenderObject::actionVerb() const
3106 {
3107 // FIXME: Need to add verbs for select elements.
3108 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
3109 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
3110 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
3111 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
3112 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
3113 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
3114 DEFINE_STATIC_LOCAL(const String, noAction, ());
3115
3116 switch (roleValue()) {
3117 case ButtonRole:
3118 return buttonAction;
3119 case TextFieldRole:
3120 case TextAreaRole:
3121 return textFieldAction;
3122 case RadioButtonRole:
3123 return radioButtonAction;
3124 case CheckBoxRole:
3125 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
3126 case LinkRole:
3127 case WebCoreLinkRole:
3128 return linkAction;
3129 default:
3130 return noAction;
3131 }
3132 }
3133
updateBackingStore()3134 void AccessibilityRenderObject::updateBackingStore()
3135 {
3136 if (!m_renderer)
3137 return;
3138
3139 // Updating layout may delete m_renderer and this object.
3140 m_renderer->document()->updateLayoutIgnorePendingStylesheets();
3141 }
3142
isLinkable(const AccessibilityRenderObject & object)3143 static bool isLinkable(const AccessibilityRenderObject& object)
3144 {
3145 if (!object.renderer())
3146 return false;
3147
3148 // See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the elements
3149 // Mozilla considers linkable.
3150 return object.isLink() || object.isImage() || object.renderer()->isText();
3151 }
3152
stringValueForMSAA() const3153 String AccessibilityRenderObject::stringValueForMSAA() const
3154 {
3155 if (isLinkable(*this)) {
3156 Element* anchor = anchorElement();
3157 if (anchor && anchor->hasTagName(aTag))
3158 return static_cast<HTMLAnchorElement*>(anchor)->href();
3159 }
3160
3161 return stringValue();
3162 }
3163
isLinked() const3164 bool AccessibilityRenderObject::isLinked() const
3165 {
3166 if (!isLinkable(*this))
3167 return false;
3168
3169 Element* anchor = anchorElement();
3170 if (!anchor || !anchor->hasTagName(aTag))
3171 return false;
3172
3173 return !static_cast<HTMLAnchorElement*>(anchor)->href().isEmpty();
3174 }
3175
nameForMSAA() const3176 String AccessibilityRenderObject::nameForMSAA() const
3177 {
3178 if (m_renderer && m_renderer->isText())
3179 return textUnderElement();
3180
3181 return title();
3182 }
3183
shouldReturnTagNameAsRoleForMSAA(const Element & element)3184 static bool shouldReturnTagNameAsRoleForMSAA(const Element& element)
3185 {
3186 // See "document structure",
3187 // https://wiki.mozilla.org/Accessibility/AT-Windows-API
3188 // FIXME: Add the other tag names that should be returned as the role.
3189 return element.hasTagName(h1Tag) || element.hasTagName(h2Tag)
3190 || element.hasTagName(h3Tag) || element.hasTagName(h4Tag)
3191 || element.hasTagName(h5Tag) || element.hasTagName(h6Tag);
3192 }
3193
stringRoleForMSAA() const3194 String AccessibilityRenderObject::stringRoleForMSAA() const
3195 {
3196 if (!m_renderer)
3197 return String();
3198
3199 Node* node = m_renderer->node();
3200 if (!node || !node->isElementNode())
3201 return String();
3202
3203 Element* element = static_cast<Element*>(node);
3204 if (!shouldReturnTagNameAsRoleForMSAA(*element))
3205 return String();
3206
3207 return element->tagName();
3208 }
3209
positionalDescriptionForMSAA() const3210 String AccessibilityRenderObject::positionalDescriptionForMSAA() const
3211 {
3212 // See "positional descriptions",
3213 // https://wiki.mozilla.org/Accessibility/AT-Windows-API
3214 if (isHeading())
3215 return "L" + String::number(headingLevel());
3216
3217 // FIXME: Add positional descriptions for other elements.
3218 return String();
3219 }
3220
descriptionForMSAA() const3221 String AccessibilityRenderObject::descriptionForMSAA() const
3222 {
3223 String description = positionalDescriptionForMSAA();
3224 if (!description.isEmpty())
3225 return description;
3226
3227 description = accessibilityDescription();
3228 if (!description.isEmpty()) {
3229 // From the Mozilla MSAA implementation:
3230 // "Signal to screen readers that this description is speakable and is not
3231 // a formatted positional information description. Don't localize the
3232 // 'Description: ' part of this string, it will be parsed out by assistive
3233 // technologies."
3234 return "Description: " + description;
3235 }
3236
3237 return String();
3238 }
3239
msaaRoleForRenderer(const RenderObject * renderer)3240 static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer)
3241 {
3242 if (!renderer)
3243 return UnknownRole;
3244
3245 if (renderer->isText())
3246 return EditableTextRole;
3247
3248 if (renderer->isListItem())
3249 return ListItemRole;
3250
3251 return UnknownRole;
3252 }
3253
roleValueForMSAA() const3254 AccessibilityRole AccessibilityRenderObject::roleValueForMSAA() const
3255 {
3256 if (m_roleForMSAA != UnknownRole)
3257 return m_roleForMSAA;
3258
3259 m_roleForMSAA = msaaRoleForRenderer(m_renderer);
3260
3261 if (m_roleForMSAA == UnknownRole)
3262 m_roleForMSAA = roleValue();
3263
3264 return m_roleForMSAA;
3265 }
3266
3267 } // namespace WebCore
3268