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 "AccessibilityListBox.h"
34 #include "AccessibilityImageMapLink.h"
35 #include "CharacterNames.h"
36 #include "EventNames.h"
37 #include "FloatRect.h"
38 #include "FocusController.h"
39 #include "Frame.h"
40 #include "FrameLoader.h"
41 #include "HTMLAreaElement.h"
42 #include "HTMLFormElement.h"
43 #include "HTMLFrameElementBase.h"
44 #include "HTMLImageElement.h"
45 #include "HTMLInputElement.h"
46 #include "HTMLLabelElement.h"
47 #include "HTMLMapElement.h"
48 #include "HTMLOptGroupElement.h"
49 #include "HTMLOptionElement.h"
50 #include "HTMLOptionsCollection.h"
51 #include "HTMLSelectElement.h"
52 #include "HTMLTextAreaElement.h"
53 #include "HitTestRequest.h"
54 #include "HitTestResult.h"
55 #include "LocalizedStrings.h"
56 #include "NodeList.h"
57 #include "Page.h"
58 #include "RenderButton.h"
59 #include "RenderFieldset.h"
60 #include "RenderFileUploadControl.h"
61 #include "RenderHTMLCanvas.h"
62 #include "RenderImage.h"
63 #include "RenderInline.h"
64 #include "RenderListBox.h"
65 #include "RenderListMarker.h"
66 #include "RenderMenuList.h"
67 #include "RenderText.h"
68 #include "RenderTextControl.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 {
90 updateAccessibilityRole();
91 #ifndef NDEBUG
92 m_renderer->setHasAXObject(true);
93 #endif
94 }
95
~AccessibilityRenderObject()96 AccessibilityRenderObject::~AccessibilityRenderObject()
97 {
98 ASSERT(isDetached());
99 }
100
create(RenderObject * renderer)101 PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer)
102 {
103 return adoptRef(new AccessibilityRenderObject(renderer));
104 }
105
detach()106 void AccessibilityRenderObject::detach()
107 {
108 clearChildren();
109 AccessibilityObject::detach();
110
111 #ifndef NDEBUG
112 if (m_renderer)
113 m_renderer->setHasAXObject(false);
114 #endif
115 m_renderer = 0;
116 }
117
firstChild() const118 AccessibilityObject* AccessibilityRenderObject::firstChild() const
119 {
120 if (!m_renderer)
121 return 0;
122
123 RenderObject* firstChild = m_renderer->firstChild();
124 if (!firstChild)
125 return 0;
126
127 return m_renderer->document()->axObjectCache()->getOrCreate(firstChild);
128 }
129
lastChild() const130 AccessibilityObject* AccessibilityRenderObject::lastChild() const
131 {
132 if (!m_renderer)
133 return 0;
134
135 RenderObject* lastChild = m_renderer->lastChild();
136 if (!lastChild)
137 return 0;
138
139 return m_renderer->document()->axObjectCache()->getOrCreate(lastChild);
140 }
141
previousSibling() const142 AccessibilityObject* AccessibilityRenderObject::previousSibling() const
143 {
144 if (!m_renderer)
145 return 0;
146
147 RenderObject* previousSibling = m_renderer->previousSibling();
148 if (!previousSibling)
149 return 0;
150
151 return m_renderer->document()->axObjectCache()->getOrCreate(previousSibling);
152 }
153
nextSibling() const154 AccessibilityObject* AccessibilityRenderObject::nextSibling() const
155 {
156 if (!m_renderer)
157 return 0;
158
159 RenderObject* nextSibling = m_renderer->nextSibling();
160 if (!nextSibling)
161 return 0;
162
163 return m_renderer->document()->axObjectCache()->getOrCreate(nextSibling);
164 }
165
parentObjectIfExists() const166 AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const
167 {
168 if (!m_renderer)
169 return 0;
170
171 RenderObject *parent = m_renderer->parent();
172 if (!parent)
173 return 0;
174
175 return m_renderer->document()->axObjectCache()->get(parent);
176 }
177
parentObject() const178 AccessibilityObject* AccessibilityRenderObject::parentObject() const
179 {
180 if (!m_renderer)
181 return 0;
182
183 RenderObject *parent = m_renderer->parent();
184 if (!parent)
185 return 0;
186
187 if (ariaRoleAttribute() == MenuBarRole)
188 return m_renderer->document()->axObjectCache()->getOrCreate(parent);
189
190 // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child
191 if (ariaRoleAttribute() == MenuRole) {
192 AccessibilityObject* parent = menuButtonForMenu();
193 if (parent)
194 return parent;
195 }
196
197 return m_renderer->document()->axObjectCache()->getOrCreate(parent);
198 }
199
isWebArea() const200 bool AccessibilityRenderObject::isWebArea() const
201 {
202 return roleValue() == WebAreaRole;
203 }
204
isImageButton() const205 bool AccessibilityRenderObject::isImageButton() const
206 {
207 return isNativeImage() && roleValue() == ButtonRole;
208 }
209
isAnchor() const210 bool AccessibilityRenderObject::isAnchor() const
211 {
212 return !isNativeImage() && isLink();
213 }
214
isNativeTextControl() const215 bool AccessibilityRenderObject::isNativeTextControl() const
216 {
217 return m_renderer->isTextControl();
218 }
219
isTextControl() const220 bool AccessibilityRenderObject::isTextControl() const
221 {
222 AccessibilityRole role = roleValue();
223 return role == TextAreaRole || role == TextFieldRole;
224 }
225
isNativeImage() const226 bool AccessibilityRenderObject::isNativeImage() const
227 {
228 return m_renderer->isImage();
229 }
230
isImage() const231 bool AccessibilityRenderObject::isImage() const
232 {
233 return roleValue() == ImageRole;
234 }
235
isAttachment() const236 bool AccessibilityRenderObject::isAttachment() const
237 {
238 if (!m_renderer)
239 return false;
240
241 // Widgets are the replaced elements that we represent to AX as attachments
242 bool isWidget = m_renderer && m_renderer->isWidget();
243 ASSERT(!isWidget || (m_renderer->isReplaced() && !isImage()));
244 return isWidget && ariaRoleAttribute() == UnknownRole;
245 }
246
isPasswordField() const247 bool AccessibilityRenderObject::isPasswordField() const
248 {
249 ASSERT(m_renderer);
250 if (!m_renderer->node() || !m_renderer->node()->isHTMLElement())
251 return false;
252 if (ariaRoleAttribute() != UnknownRole)
253 return false;
254
255 InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
256 if (!inputElement)
257 return false;
258
259 return inputElement->isPasswordField();
260 }
261
isCheckboxOrRadio() const262 bool AccessibilityRenderObject::isCheckboxOrRadio() const
263 {
264 AccessibilityRole role = roleValue();
265 return role == RadioButtonRole || role == CheckBoxRole;
266 }
267
isFileUploadButton() const268 bool AccessibilityRenderObject::isFileUploadButton() const
269 {
270 if (m_renderer && m_renderer->node() && m_renderer->node()->hasTagName(inputTag)) {
271 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node());
272 return input->inputType() == HTMLInputElement::FILE;
273 }
274
275 return false;
276 }
277
isInputImage() const278 bool AccessibilityRenderObject::isInputImage() const
279 {
280 if (m_renderer && m_renderer->node() && m_renderer->node()->hasTagName(inputTag)) {
281 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node());
282 return input->inputType() == HTMLInputElement::IMAGE;
283 }
284
285 return false;
286 }
287
isProgressIndicator() const288 bool AccessibilityRenderObject::isProgressIndicator() const
289 {
290 return roleValue() == ProgressIndicatorRole;
291 }
292
isSlider() const293 bool AccessibilityRenderObject::isSlider() const
294 {
295 return roleValue() == SliderRole;
296 }
297
isMenuRelated() const298 bool AccessibilityRenderObject::isMenuRelated() const
299 {
300 AccessibilityRole role = roleValue();
301 return role == MenuRole ||
302 role == MenuBarRole ||
303 role == MenuButtonRole ||
304 role == MenuItemRole;
305 }
306
isMenu() const307 bool AccessibilityRenderObject::isMenu() const
308 {
309 return roleValue() == MenuRole;
310 }
311
isMenuBar() const312 bool AccessibilityRenderObject::isMenuBar() const
313 {
314 return roleValue() == MenuBarRole;
315 }
316
isMenuButton() const317 bool AccessibilityRenderObject::isMenuButton() const
318 {
319 return roleValue() == MenuButtonRole;
320 }
321
isMenuItem() const322 bool AccessibilityRenderObject::isMenuItem() const
323 {
324 return roleValue() == MenuItemRole;
325 }
326
isPressed() const327 bool AccessibilityRenderObject::isPressed() const
328 {
329 ASSERT(m_renderer);
330 if (roleValue() != ButtonRole)
331 return false;
332
333 Node* node = m_renderer->node();
334 if (!node)
335 return false;
336
337 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
338 if (ariaRoleAttribute() == ButtonRole) {
339 if (equalIgnoringCase(getAttribute(aria_pressedAttr).string(), "true"))
340 return true;
341 return false;
342 }
343
344 return node->active();
345 }
346
isIndeterminate() const347 bool AccessibilityRenderObject::isIndeterminate() const
348 {
349 ASSERT(m_renderer);
350 if (!m_renderer->node() || !m_renderer->node()->isElementNode())
351 return false;
352
353 InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
354 if (!inputElement)
355 return false;
356
357 return inputElement->isIndeterminate();
358 }
359
isChecked() const360 bool AccessibilityRenderObject::isChecked() const
361 {
362 ASSERT(m_renderer);
363 if (!m_renderer->node() || !m_renderer->node()->isElementNode())
364 return false;
365
366 InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
367 if (!inputElement)
368 return false;
369
370 return inputElement->isChecked();
371 }
372
isHovered() const373 bool AccessibilityRenderObject::isHovered() const
374 {
375 ASSERT(m_renderer);
376 return m_renderer->node() && m_renderer->node()->hovered();
377 }
378
isMultiSelect() const379 bool AccessibilityRenderObject::isMultiSelect() const
380 {
381 ASSERT(m_renderer);
382 if (!m_renderer->isListBox())
383 return false;
384 return m_renderer->node() && static_cast<HTMLSelectElement*>(m_renderer->node())->multiple();
385 }
386
isReadOnly() const387 bool AccessibilityRenderObject::isReadOnly() const
388 {
389 ASSERT(m_renderer);
390
391 if (isWebArea()) {
392 Document* document = m_renderer->document();
393 if (!document)
394 return true;
395
396 HTMLElement* body = document->body();
397 if (body && body->isContentEditable())
398 return false;
399
400 Frame* frame = document->frame();
401 if (!frame)
402 return true;
403
404 return !frame->isContentEditable();
405 }
406
407 return !m_renderer->node() || !m_renderer->node()->isContentEditable();
408 }
409
isOffScreen() const410 bool AccessibilityRenderObject::isOffScreen() const
411 {
412 ASSERT(m_renderer);
413 IntRect contentRect = m_renderer->absoluteClippedOverflowRect();
414 FrameView* view = m_renderer->document()->frame()->view();
415 FloatRect viewRect = view->visibleContentRect();
416 viewRect.intersect(contentRect);
417 return viewRect.isEmpty();
418 }
419
headingLevel() const420 int AccessibilityRenderObject::headingLevel() const
421 {
422 // headings can be in block flow and non-block flow
423 if (!m_renderer)
424 return 0;
425
426 Node* node = m_renderer->node();
427 if (!node)
428 return 0;
429
430 if (ariaRoleAttribute() == HeadingRole) {
431 if (!node->isElementNode())
432 return 0;
433 Element* element = static_cast<Element*>(node);
434 return element->getAttribute(aria_levelAttr).toInt();
435 }
436
437 if (node->hasTagName(h1Tag))
438 return 1;
439
440 if (node->hasTagName(h2Tag))
441 return 2;
442
443 if (node->hasTagName(h3Tag))
444 return 3;
445
446 if (node->hasTagName(h4Tag))
447 return 4;
448
449 if (node->hasTagName(h5Tag))
450 return 5;
451
452 if (node->hasTagName(h6Tag))
453 return 6;
454
455 return 0;
456 }
457
isHeading() const458 bool AccessibilityRenderObject::isHeading() const
459 {
460 return roleValue() == HeadingRole;
461 }
462
isLink() const463 bool AccessibilityRenderObject::isLink() const
464 {
465 return roleValue() == WebCoreLinkRole;
466 }
467
isControl() const468 bool AccessibilityRenderObject::isControl() const
469 {
470 if (!m_renderer)
471 return false;
472
473 Node* node = m_renderer->node();
474 return node && ((node->isElementNode() && static_cast<Element*>(node)->isFormControlElement())
475 || AccessibilityObject::isARIAControl(ariaRoleAttribute()));
476 }
477
isFieldset() const478 bool AccessibilityRenderObject::isFieldset() const
479 {
480 if (!m_renderer)
481 return false;
482
483 return m_renderer->isFieldset();
484 }
485
isGroup() const486 bool AccessibilityRenderObject::isGroup() const
487 {
488 return roleValue() == GroupRole;
489 }
490
selectedRadioButton()491 AccessibilityObject* AccessibilityRenderObject::selectedRadioButton()
492 {
493 if (!isRadioGroup())
494 return 0;
495
496 // Find the child radio button that is selected (ie. the intValue == 1).
497 int count = m_children.size();
498 for (int i = 0; i < count; ++i) {
499 AccessibilityObject* object = m_children[i].get();
500 if (object->roleValue() == RadioButtonRole && object->intValue() == 1)
501 return object;
502 }
503 return 0;
504 }
505
getAttribute(const QualifiedName & attribute) const506 const AtomicString& AccessibilityRenderObject::getAttribute(const QualifiedName& attribute) const
507 {
508 Node* node = m_renderer->node();
509 if (!node)
510 return nullAtom;
511
512 if (!node->isElementNode())
513 return nullAtom;
514
515 Element* element = static_cast<Element*>(node);
516 return element->getAttribute(attribute);
517 }
518
anchorElement() const519 Element* AccessibilityRenderObject::anchorElement() const
520 {
521 if (!m_renderer)
522 return 0;
523
524 AXObjectCache* cache = axObjectCache();
525 RenderObject* currRenderer;
526
527 // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though.
528 for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) {
529 if (currRenderer->isRenderBlock()) {
530 RenderInline* continuation = toRenderBlock(currRenderer)->inlineContinuation();
531 if (continuation)
532 return cache->getOrCreate(continuation)->anchorElement();
533 }
534 }
535
536 // bail if none found
537 if (!currRenderer)
538 return 0;
539
540 // search up the DOM tree for an anchor element
541 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
542 Node* node = currRenderer->node();
543 for ( ; node; node = node->parentNode()) {
544 if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
545 return static_cast<Element*>(node);
546 }
547
548 return 0;
549 }
550
actionElement() const551 Element* AccessibilityRenderObject::actionElement() const
552 {
553 if (!m_renderer)
554 return 0;
555
556 Node* node = m_renderer->node();
557 if (node) {
558 if (node->hasTagName(inputTag)) {
559 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
560 if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton()))
561 return input;
562 } else if (node->hasTagName(buttonTag))
563 return static_cast<Element*>(node);
564 }
565
566 if (isFileUploadButton())
567 return static_cast<Element*>(m_renderer->node());
568
569 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
570 return static_cast<Element*>(m_renderer->node());
571
572 if (isImageButton())
573 return static_cast<Element*>(m_renderer->node());
574
575 if (m_renderer->isMenuList())
576 return static_cast<Element*>(m_renderer->node());
577
578 Element* elt = anchorElement();
579 if (!elt)
580 elt = mouseButtonListener();
581 return elt;
582 }
583
mouseButtonListener() const584 Element* AccessibilityRenderObject::mouseButtonListener() const
585 {
586 Node* node = m_renderer->node();
587 if (!node)
588 return 0;
589
590 // check if our parent is a mouse button listener
591 while (node && !node->isElementNode())
592 node = node->parent();
593
594 if (!node)
595 return 0;
596
597 // FIXME: Do the continuation search like anchorElement does
598 for (Element* element = static_cast<Element*>(node); element; element = element->parentElement()) {
599 if (element->getAttributeEventListener(eventNames().clickEvent) || element->getAttributeEventListener(eventNames().mousedownEvent) || element->getAttributeEventListener(eventNames().mouseupEvent))
600 return element;
601 }
602
603 return 0;
604 }
605
siblingWithAriaRole(String role,Node * node)606 static Element* siblingWithAriaRole(String role, Node* node)
607 {
608 Node* sibling = node->parent()->firstChild();
609 while (sibling) {
610 if (sibling->isElementNode()) {
611 String siblingAriaRole = static_cast<Element*>(sibling)->getAttribute(roleAttr).string();
612 if (equalIgnoringCase(siblingAriaRole, role))
613 return static_cast<Element*>(sibling);
614 }
615 sibling = sibling->nextSibling();
616 }
617
618 return 0;
619 }
620
menuElementForMenuButton() const621 Element* AccessibilityRenderObject::menuElementForMenuButton() const
622 {
623 if (ariaRoleAttribute() != MenuButtonRole)
624 return 0;
625
626 return siblingWithAriaRole("menu", renderer()->node());
627 }
628
menuForMenuButton() const629 AccessibilityObject* AccessibilityRenderObject::menuForMenuButton() const
630 {
631 Element* menu = menuElementForMenuButton();
632 if (menu && menu->renderer())
633 return m_renderer->document()->axObjectCache()->getOrCreate(menu->renderer());
634 return 0;
635 }
636
menuItemElementForMenu() const637 Element* AccessibilityRenderObject::menuItemElementForMenu() const
638 {
639 if (ariaRoleAttribute() != MenuRole)
640 return 0;
641
642 return siblingWithAriaRole("menuitem", renderer()->node());
643 }
644
menuButtonForMenu() const645 AccessibilityObject* AccessibilityRenderObject::menuButtonForMenu() const
646 {
647 Element* menuItem = menuItemElementForMenu();
648
649 if (menuItem && menuItem->renderer()) {
650 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
651 AccessibilityObject* menuItemAX = m_renderer->document()->axObjectCache()->getOrCreate(menuItem->renderer());
652 if (menuItemAX->isMenuButton())
653 return menuItemAX;
654 }
655 return 0;
656 }
657
helpText() const658 String AccessibilityRenderObject::helpText() const
659 {
660 if (!m_renderer)
661 return String();
662
663 for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) {
664 if (curr->node() && curr->node()->isHTMLElement()) {
665 const AtomicString& summary = static_cast<Element*>(curr->node())->getAttribute(summaryAttr);
666 if (!summary.isEmpty())
667 return summary;
668 const AtomicString& title = static_cast<Element*>(curr->node())->getAttribute(titleAttr);
669 if (!title.isEmpty())
670 return title;
671 }
672 }
673
674 return String();
675 }
676
language() const677 String AccessibilityRenderObject::language() const
678 {
679 if (!m_renderer)
680 return String();
681
682 // Defer to parent if this element doesn't have a language set
683 Node* node = m_renderer->node();
684 if (!node)
685 return AccessibilityObject::language();
686
687 if (!node->isElementNode())
688 return AccessibilityObject::language();
689
690 String language = static_cast<Element*>(node)->getAttribute(langAttr);
691 if (language.isEmpty())
692 return AccessibilityObject::language();
693 return language;
694 }
695
textUnderElement() const696 String AccessibilityRenderObject::textUnderElement() const
697 {
698 if (!m_renderer)
699 return String();
700
701 if (isFileUploadButton())
702 return toRenderFileUploadControl(m_renderer)->buttonValue();
703
704 Node* node = m_renderer->node();
705 if (node) {
706 if (Frame* frame = node->document()->frame()) {
707 // catch stale WebCoreAXObject (see <rdar://problem/3960196>)
708 if (frame->document() != node->document())
709 return String();
710 return plainText(rangeOfContents(node).get());
711 }
712 }
713
714 // return the null string for anonymous text because it is non-trivial to get
715 // the actual text and, so far, that is not needed
716 return String();
717 }
718
hasIntValue() const719 bool AccessibilityRenderObject::hasIntValue() const
720 {
721 if (isHeading())
722 return true;
723
724 if (m_renderer->node() && isCheckboxOrRadio())
725 return true;
726
727 return false;
728 }
729
intValue() const730 int AccessibilityRenderObject::intValue() const
731 {
732 if (!m_renderer || isPasswordField())
733 return 0;
734
735 if (isHeading())
736 return headingLevel();
737
738 Node* node = m_renderer->node();
739 if (!node || !isCheckboxOrRadio())
740 return 0;
741
742 // If this is an ARIA checkbox or radio, check the aria-checked attribute rather than node()->checked()
743 AccessibilityRole ariaRole = ariaRoleAttribute();
744 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
745 if (equalIgnoringCase(getAttribute(aria_checkedAttr).string(), "true"))
746 return true;
747 return false;
748 }
749
750 return static_cast<HTMLInputElement*>(node)->checked();
751 }
752
valueDescription() const753 String AccessibilityRenderObject::valueDescription() const
754 {
755 // Only sliders and progress bars support value descriptions currently.
756 if (!isProgressIndicator() && !isSlider())
757 return String();
758
759 return getAttribute(aria_valuetextAttr).string();
760 }
761
valueForRange() const762 float AccessibilityRenderObject::valueForRange() const
763 {
764 if (!isProgressIndicator() && !isSlider())
765 return 0.0f;
766
767 return getAttribute(aria_valuenowAttr).toFloat();
768 }
769
maxValueForRange() const770 float AccessibilityRenderObject::maxValueForRange() const
771 {
772 if (!isProgressIndicator() && !isSlider())
773 return 0.0f;
774
775 return getAttribute(aria_valuemaxAttr).toFloat();
776 }
777
minValueForRange() const778 float AccessibilityRenderObject::minValueForRange() const
779 {
780 if (!isProgressIndicator() && !isSlider())
781 return 0.0f;
782
783 return getAttribute(aria_valueminAttr).toFloat();
784 }
785
stringValue() const786 String AccessibilityRenderObject::stringValue() const
787 {
788 if (!m_renderer || isPasswordField())
789 return String();
790
791 if (m_renderer->isText())
792 return textUnderElement();
793
794 if (m_renderer->isMenuList())
795 return toRenderMenuList(m_renderer)->text();
796
797 if (m_renderer->isListMarker())
798 return toRenderListMarker(m_renderer)->text();
799
800 if (m_renderer->isRenderButton())
801 return toRenderButton(m_renderer)->text();
802
803 if (isWebArea()) {
804 if (m_renderer->document()->frame())
805 return String();
806
807 // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here
808 VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0);
809 VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX);
810 if (startVisiblePosition.isNull() || endVisiblePosition.isNull())
811 return String();
812
813 return plainText(makeRange(startVisiblePosition, endVisiblePosition).get());
814 }
815
816 if (isTextControl())
817 return text();
818
819 if (isFileUploadButton())
820 return toRenderFileUploadControl(m_renderer)->fileTextValue();
821
822 // FIXME: We might need to implement a value here for more types
823 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
824 // this would require subclassing or making accessibilityAttributeNames do something other than return a
825 // single static array.
826 return String();
827 }
828
829 // This function implements the ARIA accessible name as described by the Mozilla
830 // ARIA Implementer's Guide.
accessibleNameForNode(Node * node)831 static String accessibleNameForNode(Node* node)
832 {
833 if (node->isTextNode())
834 return static_cast<Text*>(node)->data();
835
836 if (node->hasTagName(inputTag))
837 return static_cast<HTMLInputElement*>(node)->value();
838
839 if (node->isHTMLElement()) {
840 const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr);
841 if (!alt.isEmpty())
842 return alt;
843 }
844
845 return String();
846 }
847
ariaAccessibilityName(const String & s) const848 String AccessibilityRenderObject::ariaAccessibilityName(const String& s) const
849 {
850 Document* document = m_renderer->document();
851 if (!document)
852 return String();
853
854 String idList = s;
855 idList.replace('\n', ' ');
856 Vector<String> idVector;
857 idList.split(' ', idVector);
858
859 Vector<UChar> ariaLabel;
860 unsigned size = idVector.size();
861 for (unsigned i = 0; i < size; ++i) {
862 String idName = idVector[i];
863 Element* idElement = document->getElementById(idName);
864 if (idElement) {
865 String nameFragment = accessibleNameForNode(idElement);
866 ariaLabel.append(nameFragment.characters(), nameFragment.length());
867 for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement)) {
868 nameFragment = accessibleNameForNode(n);
869 ariaLabel.append(nameFragment.characters(), nameFragment.length());
870 }
871
872 if (i != size - 1)
873 ariaLabel.append(' ');
874 }
875 }
876 return String::adopt(ariaLabel);
877 }
878
ariaLabeledByAttribute() const879 String AccessibilityRenderObject::ariaLabeledByAttribute() const
880 {
881 Node* node = m_renderer->node();
882 if (!node)
883 return String();
884
885 if (!node->isElementNode())
886 return String();
887
888 // The ARIA spec uses the British spelling: "labelled." It seems prudent to support the American
889 // spelling ("labeled") as well.
890 String idList = getAttribute(aria_labeledbyAttr).string();
891 if (idList.isEmpty()) {
892 idList = getAttribute(aria_labelledbyAttr).string();
893 if (idList.isEmpty())
894 return String();
895 }
896
897 return ariaAccessibilityName(idList);
898 }
899
labelForElement(Element * element)900 static HTMLLabelElement* labelForElement(Element* element)
901 {
902 RefPtr<NodeList> list = element->document()->getElementsByTagName("label");
903 unsigned len = list->length();
904 for (unsigned i = 0; i < len; i++) {
905 if (list->item(i)->hasTagName(labelTag)) {
906 HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i));
907 if (label->correspondingControl() == element)
908 return label;
909 }
910 }
911
912 return 0;
913 }
914
labelElementContainer() const915 HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const
916 {
917 if (!m_renderer)
918 return false;
919
920 // the control element should not be considered part of the label
921 if (isControl())
922 return false;
923
924 // find if this has a parent that is a label
925 for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) {
926 if (parentNode->hasTagName(labelTag))
927 return static_cast<HTMLLabelElement*>(parentNode);
928 }
929
930 return 0;
931 }
932
title() const933 String AccessibilityRenderObject::title() const
934 {
935 AccessibilityRole ariaRole = ariaRoleAttribute();
936
937 if (!m_renderer)
938 return String();
939
940 Node* node = m_renderer->node();
941 if (!node)
942 return String();
943
944 String ariaLabel = ariaLabeledByAttribute();
945 if (!ariaLabel.isEmpty())
946 return ariaLabel;
947
948 const AtomicString& title = getAttribute(titleAttr);
949 if (!title.isEmpty())
950 return title;
951
952 bool isInputTag = node->hasTagName(inputTag);
953 if (isInputTag) {
954 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
955 if (input->isTextButton())
956 return input->value();
957 }
958
959 if (isInputTag || AccessibilityObject::isARIAInput(ariaRole) || isControl()) {
960 HTMLLabelElement* label = labelForElement(static_cast<Element*>(node));
961 if (label && !titleUIElement())
962 return label->innerText();
963
964 const AtomicString& placeholder = getAttribute(placeholderAttr);
965 if (!placeholder.isEmpty())
966 return placeholder;
967 }
968
969 if (roleValue() == ButtonRole
970 || ariaRole == ListBoxOptionRole
971 || ariaRole == MenuItemRole
972 || ariaRole == MenuButtonRole
973 || ariaRole == RadioButtonRole
974 || isHeading())
975 return textUnderElement();
976
977 if (isLink())
978 return textUnderElement();
979
980 return String();
981 }
982
ariaDescribedByAttribute() const983 String AccessibilityRenderObject::ariaDescribedByAttribute() const
984 {
985 String idList = getAttribute(aria_describedbyAttr).string();
986 if (idList.isEmpty())
987 return String();
988
989 return ariaAccessibilityName(idList);
990 }
991
accessibilityDescription() const992 String AccessibilityRenderObject::accessibilityDescription() const
993 {
994 if (!m_renderer)
995 return String();
996
997 String ariaLabel = getAttribute(aria_labelAttr).string();
998 if (!ariaLabel.isEmpty())
999 return ariaLabel;
1000
1001 String ariaDescription = ariaDescribedByAttribute();
1002 if (!ariaDescription.isEmpty())
1003 return ariaDescription;
1004
1005 if (isImage() || isInputImage() || isNativeImage()) {
1006 Node* node = m_renderer->node();
1007 if (node && node->isHTMLElement()) {
1008 const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr);
1009 if (alt.isEmpty())
1010 return String();
1011 return alt;
1012 }
1013 }
1014
1015 if (isWebArea()) {
1016 Document *document = m_renderer->document();
1017 Node* owner = document->ownerElement();
1018 if (owner) {
1019 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1020 const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr);
1021 if (!title.isEmpty())
1022 return title;
1023 return static_cast<HTMLFrameElementBase*>(owner)->name();
1024 }
1025 if (owner->isHTMLElement())
1026 return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr);
1027 }
1028 owner = document->body();
1029 if (owner && owner->isHTMLElement())
1030 return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr);
1031 }
1032
1033 if (roleValue() == DefinitionListTermRole)
1034 return AXDefinitionListTermText();
1035 if (roleValue() == DefinitionListDefinitionRole)
1036 return AXDefinitionListDefinitionText();
1037
1038 return String();
1039 }
1040
boundingBoxRect() const1041 IntRect AccessibilityRenderObject::boundingBoxRect() const
1042 {
1043 RenderObject* obj = m_renderer;
1044
1045 if (!obj)
1046 return IntRect();
1047
1048 if (obj->node()) // If we are a continuation, we want to make sure to use the primary renderer.
1049 obj = obj->node()->renderer();
1050
1051 Vector<FloatQuad> quads;
1052 obj->absoluteQuads(quads);
1053 const size_t n = quads.size();
1054 if (!n)
1055 return IntRect();
1056
1057 IntRect result;
1058 for (size_t i = 0; i < n; ++i) {
1059 IntRect r = quads[i].enclosingBoundingBox();
1060 if (!r.isEmpty()) {
1061 if (obj->style()->hasAppearance())
1062 obj->theme()->adjustRepaintRect(obj, r);
1063 result.unite(r);
1064 }
1065 }
1066 return result;
1067 }
1068
checkboxOrRadioRect() const1069 IntRect AccessibilityRenderObject::checkboxOrRadioRect() const
1070 {
1071 if (!m_renderer)
1072 return IntRect();
1073
1074 HTMLLabelElement* label = labelForElement(static_cast<Element*>(m_renderer->node()));
1075 if (!label || !label->renderer())
1076 return boundingBoxRect();
1077
1078 IntRect labelRect = axObjectCache()->getOrCreate(label->renderer())->elementRect();
1079 labelRect.unite(boundingBoxRect());
1080 return labelRect;
1081 }
1082
elementRect() const1083 IntRect AccessibilityRenderObject::elementRect() const
1084 {
1085 // a checkbox or radio button should encompass its label
1086 if (isCheckboxOrRadio())
1087 return checkboxOrRadioRect();
1088
1089 return boundingBoxRect();
1090 }
1091
size() const1092 IntSize AccessibilityRenderObject::size() const
1093 {
1094 IntRect rect = elementRect();
1095 return rect.size();
1096 }
1097
clickPoint() const1098 IntPoint AccessibilityRenderObject::clickPoint() const
1099 {
1100 // use the default position unless this is an editable web area, in which case we use the selection bounds.
1101 if (!isWebArea() || isReadOnly())
1102 return AccessibilityObject::clickPoint();
1103
1104 VisibleSelection visSelection = selection();
1105 VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd());
1106 IntRect bounds = boundsForVisiblePositionRange(range);
1107 #if PLATFORM(MAC)
1108 bounds.setLocation(m_renderer->document()->view()->screenToContents(bounds.location()));
1109 #endif
1110 return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2));
1111 }
1112
internalLinkElement() const1113 AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const
1114 {
1115 Element* element = anchorElement();
1116 if (!element)
1117 return 0;
1118
1119 // Right now, we do not support ARIA links as internal link elements
1120 if (!element->hasTagName(aTag))
1121 return 0;
1122 HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(element);
1123
1124 KURL linkURL = anchor->href();
1125 String fragmentIdentifier = linkURL.fragmentIdentifier();
1126 if (fragmentIdentifier.isEmpty())
1127 return 0;
1128
1129 // check if URL is the same as current URL
1130 linkURL.removeFragmentIdentifier();
1131 if (m_renderer->document()->url() != linkURL)
1132 return 0;
1133
1134 Node* linkedNode = m_renderer->document()->findAnchor(fragmentIdentifier);
1135 if (!linkedNode)
1136 return 0;
1137
1138 // the element we find may not be accessible, keep searching until we find a good one
1139 AccessibilityObject* linkedAXElement = m_renderer->document()->axObjectCache()->getOrCreate(linkedNode->renderer());
1140 while (linkedAXElement && linkedAXElement->accessibilityIsIgnored()) {
1141 linkedNode = linkedNode->traverseNextNode();
1142
1143 while (linkedNode && !linkedNode->renderer())
1144 linkedNode = linkedNode->traverseNextSibling();
1145
1146 if (!linkedNode)
1147 return 0;
1148 linkedAXElement = m_renderer->document()->axObjectCache()->getOrCreate(linkedNode->renderer());
1149 }
1150
1151 return linkedAXElement;
1152 }
1153
addRadioButtonGroupMembers(AccessibilityChildrenVector & linkedUIElements) const1154 void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const
1155 {
1156 if (!m_renderer || roleValue() != RadioButtonRole)
1157 return;
1158
1159 Node* node = m_renderer->node();
1160 if (!node || !node->hasTagName(inputTag))
1161 return;
1162
1163 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
1164 // if there's a form, then this is easy
1165 if (input->form()) {
1166 Vector<RefPtr<Node> > formElements;
1167 input->form()->getNamedElements(input->name(), formElements);
1168
1169 unsigned len = formElements.size();
1170 for (unsigned i = 0; i < len; ++i) {
1171 Node* associateElement = formElements[i].get();
1172 if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->getOrCreate(associateElement->renderer()))
1173 linkedUIElements.append(object);
1174 }
1175 } else {
1176 RefPtr<NodeList> list = node->document()->getElementsByTagName("input");
1177 unsigned len = list->length();
1178 for (unsigned i = 0; i < len; ++i) {
1179 if (list->item(i)->hasTagName(inputTag)) {
1180 HTMLInputElement* associateElement = static_cast<HTMLInputElement*>(list->item(i));
1181 if (associateElement->isRadioButton() && associateElement->name() == input->name()) {
1182 if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->getOrCreate(associateElement->renderer()))
1183 linkedUIElements.append(object);
1184 }
1185 }
1186 }
1187 }
1188 }
1189
1190 // linked ui elements could be all the related radio buttons in a group
1191 // or an internal anchor connection
linkedUIElements(AccessibilityChildrenVector & linkedUIElements) const1192 void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const
1193 {
1194 if (isAnchor()) {
1195 AccessibilityObject* linkedAXElement = internalLinkElement();
1196 if (linkedAXElement)
1197 linkedUIElements.append(linkedAXElement);
1198 }
1199
1200 if (roleValue() == RadioButtonRole)
1201 addRadioButtonGroupMembers(linkedUIElements);
1202 }
1203
exposesTitleUIElement() const1204 bool AccessibilityRenderObject::exposesTitleUIElement() const
1205 {
1206 if (!isControl())
1207 return false;
1208
1209 // checkbox or radio buttons don't expose the title ui element unless it has a title already
1210 if (isCheckboxOrRadio() && getAttribute(titleAttr).isEmpty())
1211 return false;
1212
1213 return true;
1214 }
1215
titleUIElement() const1216 AccessibilityObject* AccessibilityRenderObject::titleUIElement() const
1217 {
1218 if (!m_renderer)
1219 return 0;
1220
1221 // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset
1222 if (isFieldset())
1223 return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend());
1224
1225 if (!exposesTitleUIElement())
1226 return 0;
1227
1228 Node* element = m_renderer->node();
1229 HTMLLabelElement* label = labelForElement(static_cast<Element*>(element));
1230 if (label && label->renderer())
1231 return axObjectCache()->getOrCreate(label->renderer());
1232
1233 return 0;
1234 }
1235
ariaIsHidden() const1236 bool AccessibilityRenderObject::ariaIsHidden() const
1237 {
1238 if (equalIgnoringCase(getAttribute(aria_hiddenAttr).string(), "true"))
1239 return true;
1240
1241 // aria-hidden hides this object and any children
1242 AccessibilityObject* object = parentObject();
1243 while (object) {
1244 if (object->isAccessibilityRenderObject() && equalIgnoringCase(static_cast<AccessibilityRenderObject*>(object)->getAttribute(aria_hiddenAttr).string(), "true"))
1245 return true;
1246 object = object->parentObject();
1247 }
1248
1249 return false;
1250 }
1251
accessibilityIsIgnored() const1252 bool AccessibilityRenderObject::accessibilityIsIgnored() const
1253 {
1254 // ignore invisible element
1255 if (!m_renderer || m_renderer->style()->visibility() != VISIBLE)
1256 return true;
1257
1258 if (ariaIsHidden())
1259 return true;
1260
1261 if (isPresentationalChildOfAriaRole())
1262 return true;
1263
1264 // ignore popup menu items because AppKit does
1265 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) {
1266 if (parent->isMenuList())
1267 return true;
1268 }
1269
1270 // find out if this element is inside of a label element.
1271 // if so, it may be ignored because it's the label for a checkbox or radio button
1272 HTMLLabelElement* labelElement = labelElementContainer();
1273 if (labelElement) {
1274 HTMLElement* correspondingControl = labelElement->correspondingControl();
1275 if (correspondingControl && correspondingControl->renderer()) {
1276 AccessibilityObject* controlObject = axObjectCache()->getOrCreate(correspondingControl->renderer());
1277 if (!controlObject->exposesTitleUIElement())
1278 return true;
1279 }
1280 }
1281
1282 AccessibilityRole ariaRole = ariaRoleAttribute();
1283 if (ariaRole == TextAreaRole || ariaRole == StaticTextRole) {
1284 String ariaText = text();
1285 return ariaText.isNull() || ariaText.isEmpty();
1286 }
1287
1288 // NOTE: BRs always have text boxes now, so the text box check here can be removed
1289 if (m_renderer->isText()) {
1290 // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level
1291 if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole ||
1292 parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole)
1293 return true;
1294 RenderText* renderText = toRenderText(m_renderer);
1295 if (m_renderer->isBR() || !renderText->firstTextBox())
1296 return true;
1297
1298 // text elements that are just empty whitespace should not be returned
1299 return renderText->text()->containsOnlyWhitespace();
1300 }
1301
1302 if (isHeading())
1303 return false;
1304
1305 if (isLink())
1306 return false;
1307
1308 // all controls are accessible
1309 if (isControl())
1310 return false;
1311
1312 // don't ignore labels, because they serve as TitleUIElements
1313 Node* node = m_renderer->node();
1314 if (node && node->hasTagName(labelTag))
1315 return false;
1316
1317 if (m_renderer->isBlockFlow() && m_renderer->childrenInline())
1318 return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener();
1319
1320 // ignore images seemingly used as spacers
1321 if (isImage()) {
1322 if (node && node->isElementNode()) {
1323 Element* elt = static_cast<Element*>(node);
1324 const AtomicString& alt = elt->getAttribute(altAttr);
1325 // don't ignore an image that has an alt tag
1326 if (!alt.isEmpty())
1327 return false;
1328 // informal standard is to ignore images with zero-length alt strings
1329 if (!alt.isNull())
1330 return true;
1331 }
1332
1333 if (node && node->hasTagName(canvasTag)) {
1334 RenderHTMLCanvas* canvas = toRenderHTMLCanvas(m_renderer);
1335 if (canvas->height() <= 1 || canvas->width() <= 1)
1336 return true;
1337 return false;
1338 }
1339
1340 if (isNativeImage()) {
1341 // check for one-dimensional image
1342 RenderImage* image = toRenderImage(m_renderer);
1343 if (image->height() <= 1 || image->width() <= 1)
1344 return true;
1345
1346 // check whether rendered image was stretched from one-dimensional file image
1347 if (image->cachedImage()) {
1348 IntSize imageSize = image->cachedImage()->imageSize(image->view()->zoomFactor());
1349 return imageSize.height() <= 1 || imageSize.width() <= 1;
1350 }
1351 }
1352 return false;
1353 }
1354
1355 if (ariaRole != UnknownRole)
1356 return false;
1357
1358 // make a platform-specific decision
1359 if (isAttachment())
1360 return accessibilityIgnoreAttachment();
1361
1362 return !m_renderer->isListMarker() && !isWebArea();
1363 }
1364
isLoaded() const1365 bool AccessibilityRenderObject::isLoaded() const
1366 {
1367 return !m_renderer->document()->tokenizer();
1368 }
1369
layoutCount() const1370 int AccessibilityRenderObject::layoutCount() const
1371 {
1372 if (!m_renderer->isRenderView())
1373 return 0;
1374 return toRenderView(m_renderer)->frameView()->layoutCount();
1375 }
1376
text() const1377 String AccessibilityRenderObject::text() const
1378 {
1379 if (!isTextControl() || isPasswordField())
1380 return String();
1381
1382 if (isNativeTextControl())
1383 return toRenderTextControl(m_renderer)->text();
1384
1385 Node* node = m_renderer->node();
1386 if (!node)
1387 return String();
1388 if (!node->isElementNode())
1389 return String();
1390
1391 return static_cast<Element*>(node)->innerText();
1392 }
1393
textLength() const1394 int AccessibilityRenderObject::textLength() const
1395 {
1396 ASSERT(isTextControl());
1397
1398 if (isPasswordField())
1399 return -1; // need to return something distinct from 0
1400
1401 return text().length();
1402 }
1403
ariaSelectedTextDOMRange() const1404 PassRefPtr<Range> AccessibilityRenderObject::ariaSelectedTextDOMRange() const
1405 {
1406 Node* node = m_renderer->node();
1407 if (!node)
1408 return 0;
1409
1410 RefPtr<Range> currentSelectionRange = selection().toNormalizedRange();
1411 if (!currentSelectionRange)
1412 return 0;
1413
1414 ExceptionCode ec = 0;
1415 if (!currentSelectionRange->intersectsNode(node, ec))
1416 return Range::create(currentSelectionRange->ownerDocument());
1417
1418 RefPtr<Range> ariaRange = rangeOfContents(node);
1419 Position startPosition, endPosition;
1420
1421 // Find intersection of currentSelectionRange and ariaRange
1422 if (ariaRange->startOffset() > currentSelectionRange->startOffset())
1423 startPosition = ariaRange->startPosition();
1424 else
1425 startPosition = currentSelectionRange->startPosition();
1426
1427 if (ariaRange->endOffset() < currentSelectionRange->endOffset())
1428 endPosition = ariaRange->endPosition();
1429 else
1430 endPosition = currentSelectionRange->endPosition();
1431
1432 return Range::create(ariaRange->ownerDocument(), startPosition, endPosition);
1433 }
1434
selectedText() const1435 String AccessibilityRenderObject::selectedText() const
1436 {
1437 ASSERT(isTextControl());
1438
1439 if (isPasswordField())
1440 return String(); // need to return something distinct from empty string
1441
1442 if (isNativeTextControl()) {
1443 RenderTextControl* textControl = toRenderTextControl(m_renderer);
1444 return textControl->text().substring(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart());
1445 }
1446
1447 if (ariaRoleAttribute() == UnknownRole)
1448 return String();
1449
1450 RefPtr<Range> ariaRange = ariaSelectedTextDOMRange();
1451 if (!ariaRange)
1452 return String();
1453 return ariaRange->text();
1454 }
1455
accessKey() const1456 const AtomicString& AccessibilityRenderObject::accessKey() const
1457 {
1458 Node* node = m_renderer->node();
1459 if (!node)
1460 return nullAtom;
1461 if (!node->isElementNode())
1462 return nullAtom;
1463 return static_cast<Element*>(node)->getAttribute(accesskeyAttr);
1464 }
1465
selection() const1466 VisibleSelection AccessibilityRenderObject::selection() const
1467 {
1468 return m_renderer->document()->frame()->selection()->selection();
1469 }
1470
selectedTextRange() const1471 PlainTextRange AccessibilityRenderObject::selectedTextRange() const
1472 {
1473 ASSERT(isTextControl());
1474
1475 if (isPasswordField())
1476 return PlainTextRange();
1477
1478 AccessibilityRole ariaRole = ariaRoleAttribute();
1479 if (isNativeTextControl() && ariaRole == UnknownRole) {
1480 RenderTextControl* textControl = toRenderTextControl(m_renderer);
1481 return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart());
1482 }
1483
1484 if (ariaRole == UnknownRole)
1485 return PlainTextRange();
1486
1487 RefPtr<Range> ariaRange = ariaSelectedTextDOMRange();
1488 if (!ariaRange)
1489 return PlainTextRange();
1490 return PlainTextRange(ariaRange->startOffset(), ariaRange->endOffset());
1491 }
1492
setSelectedTextRange(const PlainTextRange & range)1493 void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range)
1494 {
1495 if (isNativeTextControl()) {
1496 RenderTextControl* textControl = toRenderTextControl(m_renderer);
1497 textControl->setSelectionRange(range.start, range.start + range.length);
1498 return;
1499 }
1500
1501 Document* document = m_renderer->document();
1502 if (!document)
1503 return;
1504 Frame* frame = document->frame();
1505 if (!frame)
1506 return;
1507 Node* node = m_renderer->node();
1508 frame->selection()->setSelection(VisibleSelection(Position(node, range.start),
1509 Position(node, range.start + range.length), DOWNSTREAM));
1510 }
1511
url() const1512 KURL AccessibilityRenderObject::url() const
1513 {
1514 if (isAnchor() && m_renderer->node()->hasTagName(aTag)) {
1515 if (HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(anchorElement()))
1516 return anchor->href();
1517 }
1518
1519 if (isWebArea())
1520 return m_renderer->document()->url();
1521
1522 if (isImage() && m_renderer->node() && m_renderer->node()->hasTagName(imgTag))
1523 return static_cast<HTMLImageElement*>(m_renderer->node())->src();
1524
1525 if (isInputImage())
1526 return static_cast<HTMLInputElement*>(m_renderer->node())->src();
1527
1528 return KURL();
1529 }
1530
isVisited() const1531 bool AccessibilityRenderObject::isVisited() const
1532 {
1533 return m_renderer->style()->pseudoState() == PseudoVisited;
1534 }
1535
isRequired() const1536 bool AccessibilityRenderObject::isRequired() const
1537 {
1538 if (equalIgnoringCase(getAttribute(aria_requiredAttr).string(), "true"))
1539 return true;
1540
1541 return false;
1542 }
1543
isSelected() const1544 bool AccessibilityRenderObject::isSelected() const
1545 {
1546 if (!m_renderer)
1547 return false;
1548
1549 Node* node = m_renderer->node();
1550 if (!node)
1551 return false;
1552
1553 return false;
1554 }
1555
isFocused() const1556 bool AccessibilityRenderObject::isFocused() const
1557 {
1558 if (!m_renderer)
1559 return false;
1560
1561 Document* document = m_renderer->document();
1562 if (!document)
1563 return false;
1564
1565 Node* focusedNode = document->focusedNode();
1566 if (!focusedNode)
1567 return false;
1568
1569 // A web area is represented by the Document node in the DOM tree, which isn't focusable.
1570 // Check instead if the frame's selection controller is focused
1571 if (focusedNode == m_renderer->node() ||
1572 (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive()))
1573 return true;
1574
1575 return false;
1576 }
1577
setFocused(bool on)1578 void AccessibilityRenderObject::setFocused(bool on)
1579 {
1580 if (!canSetFocusAttribute())
1581 return;
1582
1583 if (!on)
1584 m_renderer->document()->setFocusedNode(0);
1585 else {
1586 if (m_renderer->node()->isElementNode())
1587 static_cast<Element*>(m_renderer->node())->focus();
1588 else
1589 m_renderer->document()->setFocusedNode(m_renderer->node());
1590 }
1591 }
1592
setValue(const String & string)1593 void AccessibilityRenderObject::setValue(const String& string)
1594 {
1595 // FIXME: Do we want to do anything here for ARIA textboxes?
1596 if (m_renderer->isTextField()) {
1597 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node());
1598 input->setValue(string);
1599 } else if (m_renderer->isTextArea()) {
1600 HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(m_renderer->node());
1601 textArea->setValue(string);
1602 }
1603 }
1604
isEnabled() const1605 bool AccessibilityRenderObject::isEnabled() const
1606 {
1607 ASSERT(m_renderer);
1608
1609 if (equalIgnoringCase(getAttribute(aria_disabledAttr).string(), "true"))
1610 return false;
1611
1612 Node* node = m_renderer->node();
1613 if (!node || !node->isElementNode())
1614 return true;
1615
1616 return static_cast<Element*>(node)->isEnabledFormControl();
1617 }
1618
topRenderer() const1619 RenderView* AccessibilityRenderObject::topRenderer() const
1620 {
1621 return m_renderer->document()->topDocument()->renderView();
1622 }
1623
document() const1624 Document* AccessibilityRenderObject::document() const
1625 {
1626 return m_renderer->document();
1627 }
1628
topDocumentFrameView() const1629 FrameView* AccessibilityRenderObject::topDocumentFrameView() const
1630 {
1631 return topRenderer()->view()->frameView();
1632 }
1633
widget() const1634 Widget* AccessibilityRenderObject::widget() const
1635 {
1636 if (!m_renderer->isWidget())
1637 return 0;
1638 return toRenderWidget(m_renderer)->widget();
1639 }
1640
axObjectCache() const1641 AXObjectCache* AccessibilityRenderObject::axObjectCache() const
1642 {
1643 return m_renderer->document()->axObjectCache();
1644 }
1645
accessibilityParentForImageMap(HTMLMapElement * map) const1646 AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const
1647 {
1648 // find an image that is using this map
1649 if (!m_renderer || !map)
1650 return 0;
1651
1652 String mapName = map->getName().string().lower();
1653 RefPtr<HTMLCollection> coll = m_renderer->document()->images();
1654 for (Node* curr = coll->firstItem(); curr; curr = coll->nextItem()) {
1655 RenderObject* obj = curr->renderer();
1656 if (!obj || !curr->hasTagName(imgTag))
1657 continue;
1658
1659 // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning,
1660 // which has to be stripped off
1661 String useMapName = static_cast<HTMLImageElement*>(curr)->useMap().substring(1).lower();
1662 if (useMapName == mapName)
1663 return axObjectCache()->getOrCreate(obj);
1664 }
1665
1666 return 0;
1667 }
1668
getDocumentLinks(AccessibilityChildrenVector & result)1669 void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result)
1670 {
1671 Document* document = m_renderer->document();
1672 RefPtr<HTMLCollection> coll = document->links();
1673 Node* curr = coll->firstItem();
1674 while (curr) {
1675 RenderObject* obj = curr->renderer();
1676 if (obj) {
1677 RefPtr<AccessibilityObject> axobj = document->axObjectCache()->getOrCreate(obj);
1678 ASSERT(axobj);
1679 if (!axobj->accessibilityIsIgnored() && axobj->isLink())
1680 result.append(axobj);
1681 } else {
1682 Node* parent = curr->parent();
1683 if (parent && curr->hasTagName(areaTag) && parent->hasTagName(mapTag)) {
1684 AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole));
1685 areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(curr));
1686 areaObject->setHTMLMapElement(static_cast<HTMLMapElement*>(parent));
1687 areaObject->setParent(accessibilityParentForImageMap(static_cast<HTMLMapElement*>(parent)));
1688
1689 result.append(areaObject);
1690 }
1691 }
1692 curr = coll->nextItem();
1693 }
1694 }
1695
documentFrameView() const1696 FrameView* AccessibilityRenderObject::documentFrameView() const
1697 {
1698 if (!m_renderer || !m_renderer->document())
1699 return 0;
1700
1701 // this is the RenderObject's Document's Frame's FrameView
1702 return m_renderer->document()->view();
1703 }
1704
widgetForAttachmentView() const1705 Widget* AccessibilityRenderObject::widgetForAttachmentView() const
1706 {
1707 if (!isAttachment())
1708 return 0;
1709 return toRenderWidget(m_renderer)->widget();
1710 }
1711
frameViewIfRenderView() const1712 FrameView* AccessibilityRenderObject::frameViewIfRenderView() const
1713 {
1714 if (!m_renderer->isRenderView())
1715 return 0;
1716 // this is the RenderObject's Document's renderer's FrameView
1717 return m_renderer->view()->frameView();
1718 }
1719
1720 // This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns
1721 // a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file
visiblePositionRange() const1722 VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const
1723 {
1724 if (!m_renderer)
1725 return VisiblePositionRange();
1726
1727 // construct VisiblePositions for start and end
1728 Node* node = m_renderer->node();
1729 if (!node)
1730 return VisiblePositionRange();
1731
1732 VisiblePosition startPos = firstDeepEditingPositionForNode(node);
1733 VisiblePosition endPos = lastDeepEditingPositionForNode(node);
1734
1735 // the VisiblePositions are equal for nodes like buttons, so adjust for that
1736 // FIXME: Really? [button, 0] and [button, 1] are distinct (before and after the button)
1737 // I expect this code is only hit for things like empty divs? In which case I don't think
1738 // the behavior is correct here -- eseidel
1739 if (startPos == endPos) {
1740 endPos = endPos.next();
1741 if (endPos.isNull())
1742 endPos = startPos;
1743 }
1744
1745 return VisiblePositionRange(startPos, endPos);
1746 }
1747
visiblePositionRangeForLine(unsigned lineCount) const1748 VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const
1749 {
1750 if (lineCount == 0 || !m_renderer)
1751 return VisiblePositionRange();
1752
1753 // iterate over the lines
1754 // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the
1755 // last offset of the last line
1756 VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForCoordinates(0, 0);
1757 VisiblePosition savedVisiblePos;
1758 while (--lineCount != 0) {
1759 savedVisiblePos = visiblePos;
1760 visiblePos = nextLinePosition(visiblePos, 0);
1761 if (visiblePos.isNull() || visiblePos == savedVisiblePos)
1762 return VisiblePositionRange();
1763 }
1764
1765 // make a caret selection for the marker position, then extend it to the line
1766 // NOTE: ignores results of sel.modify because it returns false when
1767 // starting at an empty line. The resulting selection in that case
1768 // will be a caret at visiblePos.
1769 SelectionController selection;
1770 selection.setSelection(VisibleSelection(visiblePos));
1771 selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary);
1772
1773 return VisiblePositionRange(selection.selection().visibleStart(), selection.selection().visibleEnd());
1774 }
1775
visiblePositionForIndex(int index) const1776 VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const
1777 {
1778 if (!m_renderer)
1779 return VisiblePosition();
1780
1781 if (isNativeTextControl())
1782 return toRenderTextControl(m_renderer)->visiblePositionForIndex(index);
1783
1784 if (!isTextControl() && !m_renderer->isText())
1785 return VisiblePosition();
1786
1787 Node* node = m_renderer->node();
1788 if (!node)
1789 return VisiblePosition();
1790
1791 if (index <= 0)
1792 return VisiblePosition(node, 0, DOWNSTREAM);
1793
1794 ExceptionCode ec = 0;
1795 RefPtr<Range> range = Range::create(m_renderer->document());
1796 range->selectNodeContents(node, ec);
1797 CharacterIterator it(range.get());
1798 it.advance(index - 1);
1799 return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM);
1800 }
1801
indexForVisiblePosition(const VisiblePosition & pos) const1802 int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const
1803 {
1804 if (isNativeTextControl())
1805 return toRenderTextControl(m_renderer)->indexForVisiblePosition(pos);
1806
1807 if (!isTextControl())
1808 return 0;
1809
1810 Node* node = m_renderer->node();
1811 if (!node)
1812 return 0;
1813
1814 Position indexPosition = pos.deepEquivalent();
1815 if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != node)
1816 return 0;
1817
1818 ExceptionCode ec = 0;
1819 RefPtr<Range> range = Range::create(m_renderer->document());
1820 range->setStart(node, 0, ec);
1821 range->setEnd(indexPosition.node(), indexPosition.deprecatedEditingOffset(), ec);
1822 return TextIterator::rangeLength(range.get());
1823 }
1824
boundsForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const1825 IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
1826 {
1827 if (visiblePositionRange.isNull())
1828 return IntRect();
1829
1830 // Create a mutable VisiblePositionRange.
1831 VisiblePositionRange range(visiblePositionRange);
1832 IntRect rect1 = range.start.absoluteCaretBounds();
1833 IntRect rect2 = range.end.absoluteCaretBounds();
1834
1835 // 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
1836 if (rect2.y() != rect1.y()) {
1837 VisiblePosition endOfFirstLine = endOfLine(range.start);
1838 if (range.start == endOfFirstLine) {
1839 range.start.setAffinity(DOWNSTREAM);
1840 rect1 = range.start.absoluteCaretBounds();
1841 }
1842 if (range.end == endOfFirstLine) {
1843 range.end.setAffinity(UPSTREAM);
1844 rect2 = range.end.absoluteCaretBounds();
1845 }
1846 }
1847
1848 IntRect ourrect = rect1;
1849 ourrect.unite(rect2);
1850
1851 // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead
1852 if (rect1.bottom() != rect2.bottom()) {
1853 RefPtr<Range> dataRange = makeRange(range.start, range.end);
1854 IntRect boundingBox = dataRange->boundingBox();
1855 String rangeString = plainText(dataRange.get());
1856 if (rangeString.length() > 1 && !boundingBox.isEmpty())
1857 ourrect = boundingBox;
1858 }
1859
1860 #if PLATFORM(MAC)
1861 return m_renderer->document()->view()->contentsToScreen(ourrect);
1862 #else
1863 return ourrect;
1864 #endif
1865 }
1866
setSelectedVisiblePositionRange(const VisiblePositionRange & range) const1867 void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const
1868 {
1869 if (range.start.isNull() || range.end.isNull())
1870 return;
1871
1872 // make selection and tell the document to use it. if it's zero length, then move to that position
1873 if (range.start == range.end) {
1874 m_renderer->document()->frame()->selection()->moveTo(range.start, true);
1875 }
1876 else {
1877 VisibleSelection newSelection = VisibleSelection(range.start, range.end);
1878 m_renderer->document()->frame()->selection()->setSelection(newSelection);
1879 }
1880 }
1881
visiblePositionForPoint(const IntPoint & point) const1882 VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const
1883 {
1884 // convert absolute point to view coordinates
1885 FrameView* frameView = m_renderer->document()->topDocument()->renderer()->view()->frameView();
1886 RenderView* renderView = topRenderer();
1887 Node* innerNode = 0;
1888
1889 // locate the node containing the point
1890 IntPoint pointResult;
1891 while (1) {
1892 IntPoint ourpoint;
1893 #if PLATFORM(MAC)
1894 ourpoint = frameView->screenToContents(point);
1895 #else
1896 ourpoint = point;
1897 #endif
1898 HitTestRequest request(HitTestRequest::ReadOnly |
1899 HitTestRequest::Active);
1900 HitTestResult result(ourpoint);
1901 renderView->layer()->hitTest(request, result);
1902 innerNode = result.innerNode();
1903 if (!innerNode || !innerNode->renderer())
1904 return VisiblePosition();
1905
1906 pointResult = result.localPoint();
1907
1908 // done if hit something other than a widget
1909 RenderObject* renderer = innerNode->renderer();
1910 if (!renderer->isWidget())
1911 break;
1912
1913 // descend into widget (FRAME, IFRAME, OBJECT...)
1914 Widget* widget = toRenderWidget(renderer)->widget();
1915 if (!widget || !widget->isFrameView())
1916 break;
1917 Frame* frame = static_cast<FrameView*>(widget)->frame();
1918 if (!frame)
1919 break;
1920 renderView = frame->document()->renderView();
1921 frameView = static_cast<FrameView*>(widget);
1922 }
1923
1924 return innerNode->renderer()->positionForPoint(pointResult);
1925 }
1926
1927 // NOTE: Consider providing this utility method as AX API
visiblePositionForIndex(unsigned indexValue,bool lastIndexOK) const1928 VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const
1929 {
1930 if (!isTextControl())
1931 return VisiblePosition();
1932
1933 // lastIndexOK specifies whether the position after the last character is acceptable
1934 if (indexValue >= text().length()) {
1935 if (!lastIndexOK || indexValue > text().length())
1936 return VisiblePosition();
1937 }
1938 VisiblePosition position = visiblePositionForIndex(indexValue);
1939 position.setAffinity(DOWNSTREAM);
1940 return position;
1941 }
1942
1943 // NOTE: Consider providing this utility method as AX API
index(const VisiblePosition & position) const1944 int AccessibilityRenderObject::index(const VisiblePosition& position) const
1945 {
1946 if (!isTextControl())
1947 return -1;
1948
1949 Node* node = position.deepEquivalent().node();
1950 if (!node)
1951 return -1;
1952
1953 for (RenderObject* renderer = node->renderer(); renderer && renderer->node(); renderer = renderer->parent()) {
1954 if (renderer == m_renderer)
1955 return indexForVisiblePosition(position);
1956 }
1957
1958 return -1;
1959 }
1960
1961 // Given a line number, the range of characters of the text associated with this accessibility
1962 // object that contains the line number.
doAXRangeForLine(unsigned lineNumber) const1963 PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const
1964 {
1965 if (!isTextControl())
1966 return PlainTextRange();
1967
1968 // iterate to the specified line
1969 VisiblePosition visiblePos = visiblePositionForIndex(0);
1970 VisiblePosition savedVisiblePos;
1971 for (unsigned lineCount = lineNumber; lineCount != 0; lineCount -= 1) {
1972 savedVisiblePos = visiblePos;
1973 visiblePos = nextLinePosition(visiblePos, 0);
1974 if (visiblePos.isNull() || visiblePos == savedVisiblePos)
1975 return PlainTextRange();
1976 }
1977
1978 // make a caret selection for the marker position, then extend it to the line
1979 // NOTE: ignores results of selection.modify because it returns false when
1980 // starting at an empty line. The resulting selection in that case
1981 // will be a caret at visiblePos.
1982 SelectionController selection;
1983 selection.setSelection(VisibleSelection(visiblePos));
1984 selection.modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary);
1985 selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary);
1986
1987 // calculate the indices for the selection start and end
1988 VisiblePosition startPosition = selection.selection().visibleStart();
1989 VisiblePosition endPosition = selection.selection().visibleEnd();
1990 int index1 = indexForVisiblePosition(startPosition);
1991 int index2 = indexForVisiblePosition(endPosition);
1992
1993 // add one to the end index for a line break not caused by soft line wrap (to match AppKit)
1994 if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull())
1995 index2 += 1;
1996
1997 // return nil rather than an zero-length range (to match AppKit)
1998 if (index1 == index2)
1999 return PlainTextRange();
2000
2001 return PlainTextRange(index1, index2 - index1);
2002 }
2003
2004 // The composed character range in the text associated with this accessibility object that
2005 // is specified by the given index value. This parameterized attribute returns the complete
2006 // range of characters (including surrogate pairs of multi-byte glyphs) at the given index.
doAXRangeForIndex(unsigned index) const2007 PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const
2008 {
2009 if (!isTextControl())
2010 return PlainTextRange();
2011
2012 String elementText = text();
2013 if (!elementText.length() || index > elementText.length() - 1)
2014 return PlainTextRange();
2015
2016 return PlainTextRange(index, 1);
2017 }
2018
2019 // A substring of the text associated with this accessibility object that is
2020 // specified by the given character range.
doAXStringForRange(const PlainTextRange & range) const2021 String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const
2022 {
2023 if (isPasswordField())
2024 return String();
2025
2026 if (range.length == 0)
2027 return "";
2028
2029 if (!isTextControl())
2030 return String();
2031
2032 String elementText = text();
2033 if (range.start + range.length > elementText.length())
2034 return String();
2035
2036 return elementText.substring(range.start, range.length);
2037 }
2038
2039 // The bounding rectangle of the text associated with this accessibility object that is
2040 // specified by the given range. This is the bounding rectangle a sighted user would see
2041 // on the display screen, in pixels.
doAXBoundsForRange(const PlainTextRange & range) const2042 IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const
2043 {
2044 if (isTextControl())
2045 return boundsForVisiblePositionRange(visiblePositionRangeForRange(range));
2046 return IntRect();
2047 }
2048
accessibilityImageMapHitTest(HTMLAreaElement * area,const IntPoint & point) const2049 AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const
2050 {
2051 if (!area)
2052 return 0;
2053
2054 HTMLMapElement *map = static_cast<HTMLMapElement*>(area->parent());
2055 AccessibilityObject* parent = accessibilityParentForImageMap(map);
2056 if (!parent)
2057 return 0;
2058
2059 AccessibilityObject::AccessibilityChildrenVector children = parent->children();
2060
2061 unsigned count = children.size();
2062 for (unsigned k = 0; k < count; ++k) {
2063 if (children[k]->elementRect().contains(point))
2064 return children[k].get();
2065 }
2066
2067 return 0;
2068 }
2069
doAccessibilityHitTest(const IntPoint & point) const2070 AccessibilityObject* AccessibilityRenderObject::doAccessibilityHitTest(const IntPoint& point) const
2071 {
2072 if (!m_renderer || !m_renderer->hasLayer())
2073 return 0;
2074
2075 RenderLayer* layer = toRenderBox(m_renderer)->layer();
2076
2077 HitTestRequest request(HitTestRequest::ReadOnly |
2078 HitTestRequest::Active);
2079 HitTestResult hitTestResult = HitTestResult(point);
2080 layer->hitTest(request, hitTestResult);
2081 if (!hitTestResult.innerNode())
2082 return 0;
2083 Node* node = hitTestResult.innerNode()->shadowAncestorNode();
2084
2085 if (node->hasTagName(areaTag))
2086 return accessibilityImageMapHitTest(static_cast<HTMLAreaElement*>(node), point);
2087
2088 RenderObject* obj = node->renderer();
2089 if (!obj)
2090 return 0;
2091
2092 AccessibilityObject* result = obj->document()->axObjectCache()->getOrCreate(obj);
2093
2094 if (obj->isListBox())
2095 return static_cast<AccessibilityListBox*>(result)->doAccessibilityHitTest(point);
2096
2097 if (result->accessibilityIsIgnored())
2098 result = result->parentObjectUnignored();
2099
2100 return result;
2101 }
2102
focusedUIElement() const2103 AccessibilityObject* AccessibilityRenderObject::focusedUIElement() const
2104 {
2105 // get the focused node in the page
2106 Page* page = m_renderer->document()->page();
2107 if (!page)
2108 return 0;
2109
2110 Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document();
2111 Node* focusedNode = focusedDocument->focusedNode();
2112 if (!focusedNode)
2113 focusedNode = focusedDocument;
2114
2115 RenderObject* focusedNodeRenderer = focusedNode->renderer();
2116 if (!focusedNodeRenderer)
2117 return 0;
2118
2119 AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer);
2120
2121 if (obj->shouldFocusActiveDescendant()) {
2122 if (AccessibilityObject* descendant = obj->activeDescendant())
2123 obj = descendant;
2124 }
2125
2126 // the HTML element, for example, is focusable but has an AX object that is ignored
2127 if (obj->accessibilityIsIgnored())
2128 obj = obj->parentObjectUnignored();
2129
2130 return obj;
2131 }
2132
shouldFocusActiveDescendant() const2133 bool AccessibilityRenderObject::shouldFocusActiveDescendant() const
2134 {
2135 switch (ariaRoleAttribute()) {
2136 case GroupRole:
2137 case ComboBoxRole:
2138 case ListBoxRole:
2139 case MenuRole:
2140 case MenuBarRole:
2141 case RadioGroupRole:
2142 case RowRole:
2143 case PopUpButtonRole:
2144 case ProgressIndicatorRole:
2145 case ToolbarRole:
2146 case OutlineRole:
2147 /* FIXME: replace these with actual roles when they are added to AccessibilityRole
2148 composite
2149 alert
2150 alertdialog
2151 grid
2152 status
2153 timer
2154 tree
2155 */
2156 return true;
2157 default:
2158 return false;
2159 }
2160 }
2161
activeDescendant() const2162 AccessibilityObject* AccessibilityRenderObject::activeDescendant() const
2163 {
2164 if (renderer()->node() && !renderer()->node()->isElementNode())
2165 return 0;
2166 Element* element = static_cast<Element*>(renderer()->node());
2167
2168 String activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr).string();
2169 if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty())
2170 return 0;
2171
2172 Element* target = renderer()->document()->getElementById(activeDescendantAttrStr);
2173 if (!target)
2174 return 0;
2175
2176 AccessibilityObject* obj = renderer()->document()->axObjectCache()->getOrCreate(target->renderer());
2177 if (obj->isAccessibilityRenderObject())
2178 // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification
2179 return obj;
2180 return 0;
2181 }
2182
2183
handleActiveDescendantChanged()2184 void AccessibilityRenderObject::handleActiveDescendantChanged()
2185 {
2186 Element* element = static_cast<Element*>(renderer()->node());
2187 if (!element)
2188 return;
2189 Document* doc = renderer()->document();
2190 if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedNode() != element)
2191 return;
2192 AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant());
2193
2194 if (activedescendant && shouldFocusActiveDescendant())
2195 doc->axObjectCache()->postNotification(activedescendant->renderer(), "AXFocusedUIElementChanged", true);
2196 }
2197
2198
observableObject() const2199 AccessibilityObject* AccessibilityRenderObject::observableObject() const
2200 {
2201 for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) {
2202 if (renderer->isTextControl())
2203 return renderer->document()->axObjectCache()->getOrCreate(renderer);
2204 }
2205
2206 return 0;
2207 }
2208
2209 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
2210
createARIARoleMap()2211 static const ARIARoleMap& createARIARoleMap()
2212 {
2213 struct RoleEntry {
2214 String ariaRole;
2215 AccessibilityRole webcoreRole;
2216 };
2217
2218 const RoleEntry roles[] = {
2219 { "button", ButtonRole },
2220 { "checkbox", CheckBoxRole },
2221 { "grid", TableRole },
2222 { "gridcell", CellRole },
2223 { "columnheader", ColumnHeaderRole },
2224 { "rowheader", RowHeaderRole },
2225 { "group", GroupRole },
2226 { "heading", HeadingRole },
2227 { "img", ImageRole },
2228 { "link", WebCoreLinkRole },
2229 { "listbox", ListBoxRole },
2230 // "option" isn't here because it may map to different roles depending on the parent element's role
2231 { "menu", MenuRole },
2232 { "menubar", GroupRole },
2233 // "menuitem" isn't here because it may map to different roles depending on the parent element's role
2234 { "menuitemcheckbox", MenuItemRole },
2235 { "menuitemradio", MenuItemRole },
2236 { "progressbar", ProgressIndicatorRole },
2237 { "radio", RadioButtonRole },
2238 { "radiogroup", RadioGroupRole },
2239 { "row", RowRole },
2240 { "range", SliderRole },
2241 { "slider", SliderRole },
2242 { "spinbutton", ProgressIndicatorRole },
2243 { "textbox", TextAreaRole },
2244 { "toolbar", ToolbarRole }
2245 };
2246 ARIARoleMap& roleMap = *new ARIARoleMap;
2247
2248 const unsigned numRoles = sizeof(roles) / sizeof(roles[0]);
2249 for (unsigned i = 0; i < numRoles; ++i)
2250 roleMap.set(roles[i].ariaRole, roles[i].webcoreRole);
2251 return roleMap;
2252 }
2253
ariaRoleToWebCoreRole(String value)2254 static AccessibilityRole ariaRoleToWebCoreRole(String value)
2255 {
2256 ASSERT(!value.isEmpty() && !value.isNull());
2257 static const ARIARoleMap& roleMap = createARIARoleMap();
2258 return roleMap.get(value);
2259 }
2260
determineAriaRoleAttribute() const2261 AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const
2262 {
2263 String ariaRole = getAttribute(roleAttr).string();
2264 if (ariaRole.isNull() || ariaRole.isEmpty())
2265 return UnknownRole;
2266
2267 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
2268 if (role)
2269 return role;
2270 // selects and listboxes both have options as child roles, but they map to different roles within WebCore
2271 if (equalIgnoringCase(ariaRole,"option")) {
2272 if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole)
2273 return MenuItemRole;
2274 if (parentObjectUnignored()->ariaRoleAttribute() == ListBoxRole)
2275 return ListBoxOptionRole;
2276 }
2277 // an aria "menuitem" may map to MenuButton or MenuItem depending on its parent
2278 if (equalIgnoringCase(ariaRole,"menuitem")) {
2279 if (parentObjectUnignored()->ariaRoleAttribute() == GroupRole)
2280 return MenuButtonRole;
2281 if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole)
2282 return MenuItemRole;
2283 }
2284
2285 return UnknownRole;
2286 }
2287
ariaRoleAttribute() const2288 AccessibilityRole AccessibilityRenderObject::ariaRoleAttribute() const
2289 {
2290 return m_ariaRole;
2291 }
2292
updateAccessibilityRole()2293 void AccessibilityRenderObject::updateAccessibilityRole()
2294 {
2295 m_role = determineAccessibilityRole();
2296 }
2297
determineAccessibilityRole()2298 AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole()
2299 {
2300 if (!m_renderer)
2301 return UnknownRole;
2302
2303 m_ariaRole = determineAriaRoleAttribute();
2304
2305 Node* node = m_renderer->node();
2306 AccessibilityRole ariaRole = ariaRoleAttribute();
2307 if (ariaRole != UnknownRole)
2308 return ariaRole;
2309
2310 if (node && node->isLink()) {
2311 if (m_renderer->isImage())
2312 return ImageMapRole;
2313 return WebCoreLinkRole;
2314 }
2315 if (m_renderer->isListMarker())
2316 return ListMarkerRole;
2317 if (node && node->hasTagName(buttonTag))
2318 return ButtonRole;
2319 if (m_renderer->isText())
2320 return StaticTextRole;
2321 if (m_renderer->isImage()) {
2322 if (node && node->hasTagName(inputTag))
2323 return ButtonRole;
2324 return ImageRole;
2325 }
2326 if (node && node->hasTagName(canvasTag))
2327 return ImageRole;
2328
2329 if (m_renderer->isRenderView())
2330 return WebAreaRole;
2331
2332 if (m_renderer->isTextField())
2333 return TextFieldRole;
2334
2335 if (m_renderer->isTextArea())
2336 return TextAreaRole;
2337
2338 if (node && node->hasTagName(inputTag)) {
2339 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
2340 if (input->inputType() == HTMLInputElement::CHECKBOX)
2341 return CheckBoxRole;
2342 if (input->inputType() == HTMLInputElement::RADIO)
2343 return RadioButtonRole;
2344 if (input->isTextButton())
2345 return ButtonRole;
2346 }
2347
2348 if (node && node->hasTagName(buttonTag))
2349 return ButtonRole;
2350
2351 if (isFileUploadButton())
2352 return ButtonRole;
2353
2354 if (m_renderer->isMenuList())
2355 return PopUpButtonRole;
2356
2357 if (headingLevel() != 0)
2358 return HeadingRole;
2359
2360 if (node && node->hasTagName(ddTag))
2361 return DefinitionListDefinitionRole;
2362
2363 if (node && node->hasTagName(dtTag))
2364 return DefinitionListTermRole;
2365
2366 if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag)))
2367 return AnnotationRole;
2368
2369 if (m_renderer->isBlockFlow() || (node && node->hasTagName(labelTag)))
2370 return GroupRole;
2371
2372 return UnknownRole;
2373 }
2374
isPresentationalChildOfAriaRole() const2375 bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const
2376 {
2377 // Walk the parent chain looking for a parent that has presentational children
2378 AccessibilityObject* parent;
2379 for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject())
2380 ;
2381 return parent;
2382 }
2383
ariaRoleHasPresentationalChildren() const2384 bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const
2385 {
2386 switch (m_ariaRole) {
2387 case ButtonRole:
2388 case SliderRole:
2389 case ImageRole:
2390 case ProgressIndicatorRole:
2391 //case SeparatorRole:
2392 return true;
2393 default:
2394 return false;
2395 }
2396 }
2397
canSetFocusAttribute() const2398 bool AccessibilityRenderObject::canSetFocusAttribute() const
2399 {
2400 ASSERT(m_renderer);
2401 Node* node = m_renderer->node();
2402
2403 // NOTE: It would be more accurate to ask the document whether setFocusedNode() would
2404 // do anything. For example, setFocusedNode() will do nothing if the current focused
2405 // node will not relinquish the focus.
2406 if (!node || !node->isElementNode())
2407 return false;
2408
2409 if (!static_cast<Element*>(node)->isEnabledFormControl())
2410 return false;
2411
2412 switch (roleValue()) {
2413 case WebCoreLinkRole:
2414 case ImageMapLinkRole:
2415 case TextFieldRole:
2416 case TextAreaRole:
2417 case ButtonRole:
2418 case PopUpButtonRole:
2419 case CheckBoxRole:
2420 case RadioButtonRole:
2421 return true;
2422 default:
2423 return false;
2424 }
2425 }
2426
canSetValueAttribute() const2427 bool AccessibilityRenderObject::canSetValueAttribute() const
2428 {
2429 if (equalIgnoringCase(getAttribute(aria_readonlyAttr).string(), "true"))
2430 return false;
2431
2432 if (isWebArea())
2433 return !isReadOnly();
2434
2435 return isTextControl() || isProgressIndicator() || isSlider();
2436 }
2437
canSetTextRangeAttributes() const2438 bool AccessibilityRenderObject::canSetTextRangeAttributes() const
2439 {
2440 return isTextControl();
2441 }
2442
childrenChanged()2443 void AccessibilityRenderObject::childrenChanged()
2444 {
2445 // this method is meant as a quick way of marking dirty
2446 // a portion of the accessibility tree
2447
2448 markChildrenDirty();
2449
2450 if (!m_renderer)
2451 return;
2452
2453 // Go up the render parent chain, marking children as dirty.
2454 // We can't rely on the accessibilityParent() because it may not exist and we must not create an AX object here either
2455 for (RenderObject* renderParent = m_renderer->parent(); renderParent; renderParent = renderParent->parent()) {
2456 AccessibilityObject* parent = m_renderer->document()->axObjectCache()->get(renderParent);
2457 if (parent && parent->isAccessibilityRenderObject())
2458 static_cast<AccessibilityRenderObject *>(parent)->markChildrenDirty();
2459 }
2460 }
2461
canHaveChildren() const2462 bool AccessibilityRenderObject::canHaveChildren() const
2463 {
2464 if (!m_renderer)
2465 return false;
2466
2467 // Elements that should not have children
2468 switch (roleValue()) {
2469 case ImageRole:
2470 case ButtonRole:
2471 case PopUpButtonRole:
2472 case CheckBoxRole:
2473 case RadioButtonRole:
2474 return false;
2475 default:
2476 return true;
2477 }
2478 }
2479
children()2480 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityRenderObject::children()
2481 {
2482 if (m_childrenDirty) {
2483 clearChildren();
2484 m_childrenDirty = false;
2485 }
2486
2487 if (!m_haveChildren)
2488 addChildren();
2489 return m_children;
2490 }
2491
addChildren()2492 void AccessibilityRenderObject::addChildren()
2493 {
2494 // If the need to add more children in addition to existing children arises,
2495 // childrenChanged should have been called, leaving the object with no children.
2496 ASSERT(!m_haveChildren);
2497
2498 // nothing to add if there is no RenderObject
2499 if (!m_renderer)
2500 return;
2501
2502 m_haveChildren = true;
2503
2504 if (!canHaveChildren())
2505 return;
2506
2507 // add all unignored acc children
2508 for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling()) {
2509 if (obj->accessibilityIsIgnored()) {
2510 if (!obj->hasChildren())
2511 obj->addChildren();
2512 AccessibilityChildrenVector children = obj->children();
2513 unsigned length = children.size();
2514 for (unsigned i = 0; i < length; ++i)
2515 m_children.append(children[i]);
2516 } else
2517 m_children.append(obj);
2518 }
2519
2520 // for a RenderImage, add the <area> elements as individual accessibility objects
2521 if (m_renderer->isRenderImage()) {
2522 HTMLMapElement* map = toRenderImage(m_renderer)->imageMap();
2523 if (map) {
2524 for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) {
2525
2526 // add an <area> element for this child if it has a link
2527 if (current->isLink()) {
2528 AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(m_renderer->document()->axObjectCache()->getOrCreate(ImageMapLinkRole));
2529 areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(current));
2530 areaObject->setHTMLMapElement(map);
2531 areaObject->setParent(this);
2532
2533 m_children.append(areaObject);
2534 }
2535 }
2536 }
2537 }
2538 }
2539
ariaListboxSelectedChildren(AccessibilityChildrenVector & result)2540 void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result)
2541 {
2542 AccessibilityObject* child = firstChild();
2543 bool isMultiselectable = false;
2544
2545 Element* element = static_cast<Element*>(renderer()->node());
2546 if (!element || !element->isElementNode()) // do this check to ensure safety of static_cast above
2547 return;
2548
2549 String multiselectablePropertyStr = element->getAttribute("aria-multiselectable").string();
2550 isMultiselectable = equalIgnoringCase(multiselectablePropertyStr, "true");
2551
2552 while (child) {
2553 // every child should have aria-role option, and if so, check for selected attribute/state
2554 AccessibilityRole ariaRole = child->ariaRoleAttribute();
2555 RenderObject* childRenderer = 0;
2556 if (child->isAccessibilityRenderObject())
2557 childRenderer = static_cast<AccessibilityRenderObject*>(child)->renderer();
2558 if (childRenderer && ariaRole == ListBoxOptionRole) {
2559 Element* childElement = static_cast<Element*>(childRenderer->node());
2560 if (childElement && childElement->isElementNode()) { // do this check to ensure safety of static_cast above
2561 String selectedAttrString = childElement->getAttribute("aria-selected").string();
2562 if (equalIgnoringCase(selectedAttrString, "true")) {
2563 result.append(child);
2564 if (isMultiselectable)
2565 return;
2566 }
2567 }
2568 }
2569 child = child->nextSibling();
2570 }
2571 }
2572
selectedChildren(AccessibilityChildrenVector & result)2573 void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result)
2574 {
2575 ASSERT(result.isEmpty());
2576
2577 // only listboxes should be asked for their selected children.
2578 if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes
2579 ASSERT_NOT_REACHED();
2580 return;
2581 }
2582 return ariaListboxSelectedChildren(result);
2583 }
2584
ariaListboxVisibleChildren(AccessibilityChildrenVector & result)2585 void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result)
2586 {
2587 if (!hasChildren())
2588 addChildren();
2589
2590 unsigned length = m_children.size();
2591 for (unsigned i = 0; i < length; i++) {
2592 if (!m_children[i]->isOffScreen())
2593 result.append(m_children[i]);
2594 }
2595 }
2596
visibleChildren(AccessibilityChildrenVector & result)2597 void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result)
2598 {
2599 ASSERT(result.isEmpty());
2600
2601 // only listboxes are asked for their visible children.
2602 if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes
2603 ASSERT_NOT_REACHED();
2604 return;
2605 }
2606 return ariaListboxVisibleChildren(result);
2607 }
2608
actionVerb() const2609 const String& AccessibilityRenderObject::actionVerb() const
2610 {
2611 // FIXME: Need to add verbs for select elements.
2612 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
2613 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
2614 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
2615 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
2616 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
2617 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
2618 DEFINE_STATIC_LOCAL(const String, noAction, ());
2619
2620 switch (roleValue()) {
2621 case ButtonRole:
2622 return buttonAction;
2623 case TextFieldRole:
2624 case TextAreaRole:
2625 return textFieldAction;
2626 case RadioButtonRole:
2627 return radioButtonAction;
2628 case CheckBoxRole:
2629 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
2630 case LinkRole:
2631 case WebCoreLinkRole:
2632 return linkAction;
2633 default:
2634 return noAction;
2635 }
2636 }
2637
updateBackingStore()2638 void AccessibilityRenderObject::updateBackingStore()
2639 {
2640 if (!m_renderer)
2641 return;
2642 m_renderer->view()->layoutIfNeeded();
2643 }
2644
2645 } // namespace WebCore
2646