Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(294)

Side by Side Diff: Source/core/accessibility/AXNodeObject.cpp

Issue 713933002: Create Source/modules/accessibility/ and move most of core/accessibility/* into it (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: rebase Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « Source/core/accessibility/AXNodeObject.h ('k') | Source/core/accessibility/AXObject.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2012, Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "core/accessibility/AXNodeObject.h"
31
32 #include "core/InputTypeNames.h"
33 #include "core/accessibility/AXObjectCacheImpl.h"
34 #include "core/dom/NodeTraversal.h"
35 #include "core/dom/Text.h"
36 #include "core/html/HTMLDListElement.h"
37 #include "core/html/HTMLFieldSetElement.h"
38 #include "core/html/HTMLFrameElementBase.h"
39 #include "core/html/HTMLInputElement.h"
40 #include "core/html/HTMLLabelElement.h"
41 #include "core/html/HTMLLegendElement.h"
42 #include "core/html/HTMLPlugInElement.h"
43 #include "core/html/HTMLSelectElement.h"
44 #include "core/html/HTMLTextAreaElement.h"
45 #include "core/rendering/RenderObject.h"
46 #include "platform/UserGestureIndicator.h"
47 #include "wtf/text/StringBuilder.h"
48
49
50 namespace blink {
51
52 using namespace HTMLNames;
53
54 AXNodeObject::AXNodeObject(Node* node)
55 : AXObject()
56 , m_ariaRole(UnknownRole)
57 , m_childrenDirty(false)
58 #if ENABLE(ASSERT)
59 , m_initialized(false)
60 #endif
61 , m_node(node)
62 {
63 }
64
65 PassRefPtr<AXNodeObject> AXNodeObject::create(Node* node)
66 {
67 return adoptRef(new AXNodeObject(node));
68 }
69
70 AXNodeObject::~AXNodeObject()
71 {
72 ASSERT(isDetached());
73 }
74
75 // This function implements the ARIA accessible name as described by the Mozilla
76 // ARIA Implementer's Guide.
77 static String accessibleNameForNode(Node* node)
78 {
79 if (!node)
80 return String();
81
82 if (node->isTextNode())
83 return toText(node)->data();
84
85 if (isHTMLInputElement(*node))
86 return toHTMLInputElement(*node).value();
87
88 if (node->isHTMLElement()) {
89 const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
90 if (!alt.isEmpty())
91 return alt;
92 }
93
94 return String();
95 }
96
97 String AXNodeObject::accessibilityDescriptionForElements(WillBeHeapVector<RawPtr WillBeMember<Element> > &elements) const
98 {
99 StringBuilder builder;
100 unsigned size = elements.size();
101 for (unsigned i = 0; i < size; ++i) {
102 Element* idElement = elements[i];
103
104 builder.append(accessibleNameForNode(idElement));
105 for (Node& n : NodeTraversal::descendantsOf(*idElement))
106 builder.append(accessibleNameForNode(&n));
107
108 if (i != size - 1)
109 builder.append(' ');
110 }
111 return builder.toString();
112 }
113
114 void AXNodeObject::alterSliderValue(bool increase)
115 {
116 if (roleValue() != SliderRole)
117 return;
118
119 if (!getAttribute(stepAttr).isEmpty())
120 changeValueByStep(increase);
121 else
122 changeValueByPercent(increase ? 5 : -5);
123 }
124
125 String AXNodeObject::ariaAccessibilityDescription() const
126 {
127 String ariaLabeledBy = ariaLabeledByAttribute();
128 if (!ariaLabeledBy.isEmpty())
129 return ariaLabeledBy;
130
131 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
132 if (!ariaLabel.isEmpty())
133 return ariaLabel;
134
135 return String();
136 }
137
138
139 void AXNodeObject::ariaLabeledByElements(WillBeHeapVector<RawPtrWillBeMember<Ele ment> >& elements) const
140 {
141 elementsFromAttribute(elements, aria_labeledbyAttr);
142 if (!elements.size())
143 elementsFromAttribute(elements, aria_labelledbyAttr);
144 }
145
146 void AXNodeObject::changeValueByStep(bool increase)
147 {
148 float step = stepValueForRange();
149 float value = valueForRange();
150
151 value += increase ? step : -step;
152
153 setValue(String::number(value));
154
155 axObjectCache()->postNotification(node(), AXObjectCacheImpl::AXValueChanged, true);
156 }
157
158 bool AXNodeObject::computeAccessibilityIsIgnored() const
159 {
160 #if ENABLE(ASSERT)
161 // Double-check that an AXObject is never accessed before
162 // it's been initialized.
163 ASSERT(m_initialized);
164 #endif
165
166 // If this element is within a parent that cannot have children, it should n ot be exposed.
167 if (isDescendantOfBarrenParent())
168 return true;
169
170 // Ignore labels that are already referenced by a control's title UI element .
171 AXObject* controlObject = correspondingControlForLabelElement();
172 if (controlObject && !controlObject->exposesTitleUIElement() && controlObjec t->isCheckboxOrRadio())
173 return true;
174
175 return m_role == UnknownRole;
176 }
177
178 AccessibilityRole AXNodeObject::determineAccessibilityRole()
179 {
180 if (!node())
181 return UnknownRole;
182
183 m_ariaRole = determineAriaRoleAttribute();
184
185 AccessibilityRole ariaRole = ariaRoleAttribute();
186 if (ariaRole != UnknownRole)
187 return ariaRole;
188
189 if (node()->isLink())
190 return LinkRole;
191 if (node()->isTextNode())
192 return StaticTextRole;
193 if (isHTMLButtonElement(*node()))
194 return buttonRoleType();
195 if (isHTMLDetailsElement(*node()))
196 return DetailsRole;
197 if (isHTMLSummaryElement(*node())) {
198 if (node()->parentNode() && isHTMLDetailsElement(node()->parentNode()))
199 return DisclosureTriangleRole;
200 return UnknownRole;
201 }
202
203 if (isHTMLInputElement(*node())) {
204 HTMLInputElement& input = toHTMLInputElement(*node());
205 const AtomicString& type = input.type();
206 if (type == InputTypeNames::button) {
207 if ((node()->parentNode() && isHTMLMenuElement(node()->parentNode()) ) || (parentObject() && parentObject()->roleValue() == MenuRole))
208 return MenuItemRole;
209 return buttonRoleType();
210 }
211 if (type == InputTypeNames::checkbox) {
212 if ((node()->parentNode() && isHTMLMenuElement(node()->parentNode()) ) || (parentObject() && parentObject()->roleValue() == MenuRole))
213 return MenuItemCheckBoxRole;
214 return CheckBoxRole;
215 }
216 if (type == InputTypeNames::date)
217 return DateRole;
218 if (type == InputTypeNames::datetime
219 || type == InputTypeNames::datetime_local
220 || type == InputTypeNames::month
221 || type == InputTypeNames::week)
222 return DateTimeRole;
223 if (type == InputTypeNames::radio) {
224 if ((node()->parentNode() && isHTMLMenuElement(node()->parentNode()) ) || (parentObject() && parentObject()->roleValue() == MenuRole))
225 return MenuItemRadioRole;
226 return RadioButtonRole;
227 }
228 if (input.isTextButton())
229 return buttonRoleType();
230 if (type == InputTypeNames::range)
231 return SliderRole;
232 if (type == InputTypeNames::color)
233 return ColorWellRole;
234 if (type == InputTypeNames::time)
235 return TimeRole;
236 return TextFieldRole;
237 }
238 if (isHTMLSelectElement(*node())) {
239 HTMLSelectElement& selectElement = toHTMLSelectElement(*node());
240 return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
241 }
242 if (isHTMLTextAreaElement(*node()))
243 return TextAreaRole;
244 if (headingLevel())
245 return HeadingRole;
246 if (isHTMLDivElement(*node()))
247 return DivRole;
248 if (isHTMLMeterElement(*node()))
249 return MeterRole;
250 if (isHTMLOutputElement(*node()))
251 return StatusRole;
252 if (isHTMLParagraphElement(*node()))
253 return ParagraphRole;
254 if (isHTMLLabelElement(*node()))
255 return LabelRole;
256 if (isHTMLRubyElement(*node()))
257 return RubyRole;
258 if (isHTMLDListElement(*node()))
259 return DescriptionListRole;
260 if (node()->isElementNode() && node()->hasTagName(blockquoteTag))
261 return BlockquoteRole;
262 if (node()->isElementNode() && node()->hasTagName(figcaptionTag))
263 return FigcaptionRole;
264 if (node()->isElementNode() && node()->hasTagName(figureTag))
265 return FigureRole;
266 if (node()->isElementNode() && toElement(node())->isFocusable())
267 return GroupRole;
268 if (isHTMLAnchorElement(*node()) && isClickable())
269 return LinkRole;
270 if (isHTMLIFrameElement(*node()))
271 return IframeRole;
272 if (isEmbeddedObject())
273 return EmbeddedObjectRole;
274
275 return UnknownRole;
276 }
277
278 AccessibilityRole AXNodeObject::determineAriaRoleAttribute() const
279 {
280 const AtomicString& ariaRole = getAttribute(roleAttr);
281 if (ariaRole.isNull() || ariaRole.isEmpty())
282 return UnknownRole;
283
284 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
285
286 // ARIA states if an item can get focus, it should not be presentational.
287 if ((role == NoneRole || role == PresentationalRole) && canSetFocusAttribute ())
288 return UnknownRole;
289
290 if (role == ButtonRole)
291 role = buttonRoleType();
292
293 if (role == TextAreaRole && !ariaIsMultiline())
294 role = TextFieldRole;
295
296 role = remapAriaRoleDueToParent(role);
297
298 if (role)
299 return role;
300
301 return UnknownRole;
302 }
303
304 void AXNodeObject::elementsFromAttribute(WillBeHeapVector<RawPtrWillBeMember<Ele ment> >& elements, const QualifiedName& attribute) const
305 {
306 Node* node = this->node();
307 if (!node || !node->isElementNode())
308 return;
309
310 TreeScope& scope = node->treeScope();
311
312 String idList = getAttribute(attribute).string();
313 if (idList.isEmpty())
314 return;
315
316 idList.replace('\n', ' ');
317 Vector<String> idVector;
318 idList.split(' ', idVector);
319
320 unsigned size = idVector.size();
321 for (unsigned i = 0; i < size; ++i) {
322 AtomicString idName(idVector[i]);
323 Element* idElement = scope.getElementById(idName);
324 if (idElement)
325 elements.append(idElement);
326 }
327 }
328
329 // If you call node->hasEditableStyle() since that will return true if an ancest or is editable.
330 // This only returns true if this is the element that actually has the contentEd itable attribute set.
331 bool AXNodeObject::hasContentEditableAttributeSet() const
332 {
333 if (!hasAttribute(contenteditableAttr))
334 return false;
335 const AtomicString& contentEditableValue = getAttribute(contenteditableAttr) ;
336 // Both "true" (case-insensitive) and the empty string count as true.
337 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableVa lue, "true");
338 }
339
340 bool AXNodeObject::isDescendantOfBarrenParent() const
341 {
342 for (AXObject* object = parentObject(); object; object = object->parentObjec t()) {
343 if (!object->canHaveChildren())
344 return true;
345 }
346
347 return false;
348 }
349
350 bool AXNodeObject::isGenericFocusableElement() const
351 {
352 if (!canSetFocusAttribute())
353 return false;
354
355 // If it's a control, it's not generic.
356 if (isControl())
357 return false;
358
359 // If it has an aria role, it's not generic.
360 if (m_ariaRole != UnknownRole)
361 return false;
362
363 // If the content editable attribute is set on this element, that's the reas on
364 // it's focusable, and existing logic should handle this case already - so i t's not a
365 // generic focusable element.
366
367 if (hasContentEditableAttributeSet())
368 return false;
369
370 // The web area and body element are both focusable, but existing logic hand les these
371 // cases already, so we don't need to include them here.
372 if (roleValue() == WebAreaRole)
373 return false;
374 if (isHTMLBodyElement(node()))
375 return false;
376
377 // An SVG root is focusable by default, but it's probably not interactive, s o don't
378 // include it. It can still be made accessible by giving it an ARIA role.
379 if (roleValue() == SVGRootRole)
380 return false;
381
382 return true;
383 }
384
385 HTMLLabelElement* AXNodeObject::labelForElement(Element* element) const
386 {
387 if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
388 return 0;
389
390 const AtomicString& id = element->getIdAttribute();
391 if (!id.isEmpty()) {
392 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id) )
393 return label;
394 }
395
396 return Traversal<HTMLLabelElement>::firstAncestor(*element);
397 }
398
399 AXObject* AXNodeObject::menuButtonForMenu() const
400 {
401 Element* menuItem = menuItemElementForMenu();
402
403 if (menuItem) {
404 // ARIA just has generic menu items. AppKit needs to know if this is a t op level items like MenuBarButton or MenuBarItem
405 AXObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
406 if (menuItemAX && menuItemAX->isMenuButton())
407 return menuItemAX;
408 }
409 return 0;
410 }
411
412 static Element* siblingWithAriaRole(String role, Node* node)
413 {
414 Node* parent = node->parentNode();
415 if (!parent)
416 return 0;
417
418 for (Element* sibling = ElementTraversal::firstChild(*parent); sibling; sibl ing = ElementTraversal::nextSibling(*sibling)) {
419 const AtomicString& siblingAriaRole = sibling->getAttribute(roleAttr);
420 if (equalIgnoringCase(siblingAriaRole, role))
421 return sibling;
422 }
423
424 return 0;
425 }
426
427 Element* AXNodeObject::menuItemElementForMenu() const
428 {
429 if (ariaRoleAttribute() != MenuRole)
430 return 0;
431
432 return siblingWithAriaRole("menuitem", node());
433 }
434
435 Element* AXNodeObject::mouseButtonListener() const
436 {
437 Node* node = this->node();
438 if (!node)
439 return 0;
440
441 // check if our parent is a mouse button listener
442 if (!node->isElementNode())
443 node = node->parentElement();
444
445 if (!node)
446 return 0;
447
448 // FIXME: Do the continuation search like anchorElement does
449 for (Element* element = toElement(node); element; element = element->parentE lement()) {
450 if (element->getAttributeEventListener(EventTypeNames::click) || element ->getAttributeEventListener(EventTypeNames::mousedown) || element->getAttributeE ventListener(EventTypeNames::mouseup))
451 return element;
452 }
453
454 return 0;
455 }
456
457 AccessibilityRole AXNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
458 {
459 // Some objects change their role based on their parent.
460 // However, asking for the unignoredParent calls accessibilityIsIgnored(), w hich can trigger a loop.
461 // While inside the call stack of creating an element, we need to avoid acce ssibilityIsIgnored().
462 // https://bugs.webkit.org/show_bug.cgi?id=65174
463
464 if (role != ListBoxOptionRole && role != MenuItemRole)
465 return role;
466
467 for (AXObject* parent = parentObject(); parent && !parent->accessibilityIsIg nored(); parent = parent->parentObject()) {
468 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
469
470 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
471 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
472 return MenuItemRole;
473 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
474 if (role == MenuItemRole && parentAriaRole == GroupRole)
475 return MenuButtonRole;
476
477 // If the parent had a different role, then we don't need to continue se arching up the chain.
478 if (parentAriaRole)
479 break;
480 }
481
482 return role;
483 }
484
485 void AXNodeObject::init()
486 {
487 #if ENABLE(ASSERT)
488 ASSERT(!m_initialized);
489 m_initialized = true;
490 #endif
491 m_role = determineAccessibilityRole();
492 }
493
494 void AXNodeObject::detach()
495 {
496 clearChildren();
497 AXObject::detach();
498 m_node = 0;
499 }
500
501 bool AXNodeObject::isAnchor() const
502 {
503 return !isNativeImage() && isLink();
504 }
505
506 bool AXNodeObject::isControl() const
507 {
508 Node* node = this->node();
509 if (!node)
510 return false;
511
512 return ((node->isElementNode() && toElement(node)->isFormControlElement())
513 || AXObject::isARIAControl(ariaRoleAttribute()));
514 }
515
516 bool AXNodeObject::isEmbeddedObject() const
517 {
518 return isHTMLPlugInElement(node());
519 }
520
521 bool AXNodeObject::isFieldset() const
522 {
523 return isHTMLFieldSetElement(node());
524 }
525
526 bool AXNodeObject::isHeading() const
527 {
528 return roleValue() == HeadingRole;
529 }
530
531 bool AXNodeObject::isHovered() const
532 {
533 Node* node = this->node();
534 if (!node)
535 return false;
536
537 return node->hovered();
538 }
539
540 bool AXNodeObject::isImage() const
541 {
542 return roleValue() == ImageRole;
543 }
544
545 bool AXNodeObject::isImageButton() const
546 {
547 return isNativeImage() && isButton();
548 }
549
550 bool AXNodeObject::isInputImage() const
551 {
552 Node* node = this->node();
553 if (roleValue() == ButtonRole && isHTMLInputElement(node))
554 return toHTMLInputElement(*node).type() == InputTypeNames::image;
555
556 return false;
557 }
558
559 bool AXNodeObject::isLink() const
560 {
561 return roleValue() == LinkRole;
562 }
563
564 bool AXNodeObject::isMenu() const
565 {
566 return roleValue() == MenuRole;
567 }
568
569 bool AXNodeObject::isMenuButton() const
570 {
571 return roleValue() == MenuButtonRole;
572 }
573
574 bool AXNodeObject::isMultiSelectable() const
575 {
576 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableA ttr);
577 if (equalIgnoringCase(ariaMultiSelectable, "true"))
578 return true;
579 if (equalIgnoringCase(ariaMultiSelectable, "false"))
580 return false;
581
582 return isHTMLSelectElement(node()) && toHTMLSelectElement(*node()).multiple( );
583 }
584
585 bool AXNodeObject::isNativeCheckboxOrRadio() const
586 {
587 Node* node = this->node();
588 if (!isHTMLInputElement(node))
589 return false;
590
591 HTMLInputElement* input = toHTMLInputElement(node);
592 return input->type() == InputTypeNames::checkbox || input->type() == InputTy peNames::radio;
593 }
594
595 bool AXNodeObject::isNativeImage() const
596 {
597 Node* node = this->node();
598 if (!node)
599 return false;
600
601 if (isHTMLImageElement(*node))
602 return true;
603
604 if (isHTMLPlugInElement(*node))
605 return true;
606
607 if (isHTMLInputElement(*node))
608 return toHTMLInputElement(*node).type() == InputTypeNames::image;
609
610 return false;
611 }
612
613 bool AXNodeObject::isNativeTextControl() const
614 {
615 Node* node = this->node();
616 if (!node)
617 return false;
618
619 if (isHTMLTextAreaElement(*node))
620 return true;
621
622 if (isHTMLInputElement(*node))
623 return toHTMLInputElement(node)->isTextField();
624
625 return false;
626 }
627
628 bool AXNodeObject::isNonNativeTextControl() const
629 {
630 if (isNativeTextControl())
631 return false;
632
633 if (hasContentEditableAttributeSet())
634 return true;
635
636 if (isARIATextControl())
637 return true;
638
639 return false;
640 }
641
642 bool AXNodeObject::isPasswordField() const
643 {
644 Node* node = this->node();
645 if (!isHTMLInputElement(node))
646 return false;
647
648 if (ariaRoleAttribute() != UnknownRole)
649 return false;
650
651 return toHTMLInputElement(node)->type() == InputTypeNames::password;
652 }
653
654 bool AXNodeObject::isProgressIndicator() const
655 {
656 return roleValue() == ProgressIndicatorRole;
657 }
658
659 bool AXNodeObject::isSlider() const
660 {
661 return roleValue() == SliderRole;
662 }
663
664 bool AXNodeObject::isChecked() const
665 {
666 Node* node = this->node();
667 if (!node)
668 return false;
669
670 // First test for native checkedness semantics
671 if (isHTMLInputElement(*node))
672 return toHTMLInputElement(*node).shouldAppearChecked();
673
674 // Else, if this is an ARIA checkbox or radio OR ARIA role menuitemcheckbox
675 // or menuitemradio, respect the aria-checked attribute
676 AccessibilityRole ariaRole = ariaRoleAttribute();
677 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole
678 || ariaRole == MenuItemCheckBoxRole || ariaRole == MenuItemRadioRole) {
679 if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
680 return true;
681 return false;
682 }
683
684 // Otherwise it's not checked
685 return false;
686 }
687
688 bool AXNodeObject::isClickable() const
689 {
690 if (node()) {
691 if (node()->isElementNode() && toElement(node())->isDisabledFormControl( ))
692 return false;
693
694 // Note: we can't call node()->willRespondToMouseClickEvents() because t hat triggers a style recalc and can delete this.
695 if (node()->hasEventListeners(EventTypeNames::mouseup) || node()->hasEve ntListeners(EventTypeNames::mousedown) || node()->hasEventListeners(EventTypeNam es::click) || node()->hasEventListeners(EventTypeNames::DOMActivate))
696 return true;
697 }
698
699 return AXObject::isClickable();
700 }
701
702 bool AXNodeObject::isEnabled() const
703 {
704 if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true"))
705 return false;
706
707 Node* node = this->node();
708 if (!node || !node->isElementNode())
709 return true;
710
711 return !toElement(node)->isDisabledFormControl();
712 }
713
714 AccessibilityExpanded AXNodeObject::isExpanded() const
715 {
716 const AtomicString& expanded = getAttribute(aria_expandedAttr);
717 if (equalIgnoringCase(expanded, "true"))
718 return ExpandedExpanded;
719 if (equalIgnoringCase(expanded, "false"))
720 return ExpandedCollapsed;
721
722 return ExpandedUndefined;
723 }
724
725 bool AXNodeObject::isIndeterminate() const
726 {
727 Node* node = this->node();
728 if (!isHTMLInputElement(node))
729 return false;
730
731 return toHTMLInputElement(node)->shouldAppearIndeterminate();
732 }
733
734 bool AXNodeObject::isPressed() const
735 {
736 if (!isButton())
737 return false;
738
739 Node* node = this->node();
740 if (!node)
741 return false;
742
743 // If this is an ARIA button, check the aria-pressed attribute rather than n ode()->active()
744 if (ariaRoleAttribute() == ButtonRole) {
745 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
746 return true;
747 return false;
748 }
749
750 return node->active();
751 }
752
753 bool AXNodeObject::isReadOnly() const
754 {
755 Node* node = this->node();
756 if (!node)
757 return true;
758
759 if (isHTMLTextAreaElement(*node))
760 return toHTMLTextAreaElement(*node).isReadOnly();
761
762 if (isHTMLInputElement(*node)) {
763 HTMLInputElement& input = toHTMLInputElement(*node);
764 if (input.isTextField())
765 return input.isReadOnly();
766 }
767
768 return !node->hasEditableStyle();
769 }
770
771 bool AXNodeObject::isRequired() const
772 {
773 if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
774 return true;
775
776 Node* n = this->node();
777 if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
778 return toHTMLFormControlElement(n)->isRequired();
779
780 return false;
781 }
782
783 bool AXNodeObject::canSetFocusAttribute() const
784 {
785 Node* node = this->node();
786 if (!node)
787 return false;
788
789 if (isWebArea())
790 return true;
791
792 // NOTE: It would be more accurate to ask the document whether setFocusedNod e() would
793 // do anything. For example, setFocusedNode() will do nothing if the current focused
794 // node will not relinquish the focus.
795 if (!node)
796 return false;
797
798 if (isDisabledFormControl(node))
799 return false;
800
801 return node->isElementNode() && toElement(node)->supportsFocus();
802 }
803
804 bool AXNodeObject::canSetValueAttribute() const
805 {
806 if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true"))
807 return false;
808
809 if (isProgressIndicator() || isSlider())
810 return true;
811
812 if (isTextControl() && !isNativeTextControl())
813 return true;
814
815 // Any node could be contenteditable, so isReadOnly should be relied upon
816 // for this information for all elements.
817 return !isReadOnly();
818 }
819
820 bool AXNodeObject::canvasHasFallbackContent() const
821 {
822 Node* node = this->node();
823 if (!isHTMLCanvasElement(node))
824 return false;
825
826 // If it has any children that are elements, we'll assume it might be fallba ck
827 // content. If it has no children or its only children are not elements
828 // (e.g. just text nodes), it doesn't have fallback content.
829 return ElementTraversal::firstChild(*node);
830 }
831
832 bool AXNodeObject::exposesTitleUIElement() const
833 {
834 if (!isControl())
835 return false;
836
837 // If this control is ignored (because it's invisible),
838 // then the label needs to be exposed so it can be visible to accessibility.
839 if (accessibilityIsIgnored())
840 return true;
841
842 // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should
843 // override the "label" element association.
844 bool hasTextAlternative = (!ariaLabeledByAttribute().isEmpty() || !getAttrib ute(aria_labelAttr).isEmpty());
845
846 // Checkboxes and radio buttons use the text of their title ui element as th eir own AXTitle.
847 // This code controls whether the title ui element should appear in the AX t ree (usually, no).
848 // It should appear if the control already has a label (which will be used a s the AXTitle instead).
849 if (isCheckboxOrRadio())
850 return hasTextAlternative;
851
852 // When controls have their own descriptions, the title element should be ig nored.
853 if (hasTextAlternative)
854 return false;
855
856 return true;
857 }
858
859 int AXNodeObject::headingLevel() const
860 {
861 // headings can be in block flow and non-block flow
862 Node* node = this->node();
863 if (!node)
864 return 0;
865
866 if (ariaRoleAttribute() == HeadingRole)
867 return getAttribute(aria_levelAttr).toInt();
868
869 if (!node->isHTMLElement())
870 return 0;
871
872 HTMLElement& element = toHTMLElement(*node);
873 if (element.hasTagName(h1Tag))
874 return 1;
875
876 if (element.hasTagName(h2Tag))
877 return 2;
878
879 if (element.hasTagName(h3Tag))
880 return 3;
881
882 if (element.hasTagName(h4Tag))
883 return 4;
884
885 if (element.hasTagName(h5Tag))
886 return 5;
887
888 if (element.hasTagName(h6Tag))
889 return 6;
890
891 return 0;
892 }
893
894 unsigned AXNodeObject::hierarchicalLevel() const
895 {
896 Node* node = this->node();
897 if (!node || !node->isElementNode())
898 return 0;
899 Element* element = toElement(node);
900 String ariaLevel = element->getAttribute(aria_levelAttr);
901 if (!ariaLevel.isEmpty())
902 return ariaLevel.toInt();
903
904 // Only tree item will calculate its level through the DOM currently.
905 if (roleValue() != TreeItemRole)
906 return 0;
907
908 // Hierarchy leveling starts at 1, to match the aria-level spec.
909 // We measure tree hierarchy by the number of groups that the item is within .
910 unsigned level = 1;
911 for (AXObject* parent = parentObject(); parent; parent = parent->parentObjec t()) {
912 AccessibilityRole parentRole = parent->roleValue();
913 if (parentRole == GroupRole)
914 level++;
915 else if (parentRole == TreeRole)
916 break;
917 }
918
919 return level;
920 }
921
922 String AXNodeObject::text() const
923 {
924 // If this is a user defined static text, use the accessible name computatio n.
925 if (ariaRoleAttribute() == StaticTextRole)
926 return ariaAccessibilityDescription();
927
928 if (!isTextControl())
929 return String();
930
931 Node* node = this->node();
932 if (!node)
933 return String();
934
935 if (isNativeTextControl() && (isHTMLTextAreaElement(*node) || isHTMLInputEle ment(*node)))
936 return toHTMLTextFormControlElement(*node).value();
937
938 if (!node->isElementNode())
939 return String();
940
941 return toElement(node)->innerText();
942 }
943
944 AXObject* AXNodeObject::titleUIElement() const
945 {
946 if (!node() || !node()->isElementNode())
947 return 0;
948
949 if (isFieldset())
950 return axObjectCache()->getOrCreate(toHTMLFieldSetElement(node())->legen d());
951
952 HTMLLabelElement* label = labelForElement(toElement(node()));
953 if (label)
954 return axObjectCache()->getOrCreate(label);
955
956 return 0;
957 }
958
959 AccessibilityButtonState AXNodeObject::checkboxOrRadioValue() const
960 {
961 if (isNativeCheckboxOrRadio())
962 return isChecked() ? ButtonStateOn : ButtonStateOff;
963
964 return AXObject::checkboxOrRadioValue();
965 }
966
967 void AXNodeObject::colorValue(int& r, int& g, int& b) const
968 {
969 r = 0;
970 g = 0;
971 b = 0;
972
973 if (!isColorWell())
974 return;
975
976 if (!isHTMLInputElement(node()))
977 return;
978
979 HTMLInputElement* input = toHTMLInputElement(node());
980 const AtomicString& type = input->getAttribute(typeAttr);
981 if (!equalIgnoringCase(type, "color"))
982 return;
983
984 // HTMLInputElement::value always returns a string parseable by Color.
985 Color color;
986 bool success = color.setFromString(input->value());
987 ASSERT_UNUSED(success, success);
988 r = color.red();
989 g = color.green();
990 b = color.blue();
991 }
992
993 String AXNodeObject::valueDescription() const
994 {
995 if (!supportsRangeValue())
996 return String();
997
998 return getAttribute(aria_valuetextAttr).string();
999 }
1000
1001 float AXNodeObject::valueForRange() const
1002 {
1003 if (hasAttribute(aria_valuenowAttr))
1004 return getAttribute(aria_valuenowAttr).toFloat();
1005
1006 if (isHTMLInputElement(node())) {
1007 HTMLInputElement& input = toHTMLInputElement(*node());
1008 if (input.type() == InputTypeNames::range)
1009 return input.valueAsNumber();
1010 }
1011
1012 return 0.0;
1013 }
1014
1015 float AXNodeObject::maxValueForRange() const
1016 {
1017 if (hasAttribute(aria_valuemaxAttr))
1018 return getAttribute(aria_valuemaxAttr).toFloat();
1019
1020 if (isHTMLInputElement(node())) {
1021 HTMLInputElement& input = toHTMLInputElement(*node());
1022 if (input.type() == InputTypeNames::range)
1023 return input.maximum();
1024 }
1025
1026 return 0.0;
1027 }
1028
1029 float AXNodeObject::minValueForRange() const
1030 {
1031 if (hasAttribute(aria_valueminAttr))
1032 return getAttribute(aria_valueminAttr).toFloat();
1033
1034 if (isHTMLInputElement(node())) {
1035 HTMLInputElement& input = toHTMLInputElement(*node());
1036 if (input.type() == InputTypeNames::range)
1037 return input.minimum();
1038 }
1039
1040 return 0.0;
1041 }
1042
1043 float AXNodeObject::stepValueForRange() const
1044 {
1045 return getAttribute(stepAttr).toFloat();
1046 }
1047
1048 String AXNodeObject::stringValue() const
1049 {
1050 Node* node = this->node();
1051 if (!node)
1052 return String();
1053
1054 if (ariaRoleAttribute() == StaticTextRole) {
1055 String staticText = text();
1056 if (!staticText.length())
1057 staticText = textUnderElement();
1058 return staticText;
1059 }
1060
1061 if (node->isTextNode())
1062 return textUnderElement();
1063
1064 if (isHTMLSelectElement(*node)) {
1065 HTMLSelectElement& selectElement = toHTMLSelectElement(*node);
1066 int selectedIndex = selectElement.selectedIndex();
1067 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = se lectElement.listItems();
1068 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems .size()) {
1069 const AtomicString& overriddenDescription = listItems[selectedIndex] ->fastGetAttribute(aria_labelAttr);
1070 if (!overriddenDescription.isNull())
1071 return overriddenDescription;
1072 }
1073 if (!selectElement.multiple())
1074 return selectElement.value();
1075 return String();
1076 }
1077
1078 if (isTextControl())
1079 return text();
1080
1081 // FIXME: We might need to implement a value here for more types
1082 // FIXME: It would be better not to advertise a value at all for the types f or which we don't implement one;
1083 // this would require subclassing or making accessibilityAttributeNames do s omething other than return a
1084 // single static array.
1085 return String();
1086 }
1087
1088
1089 const AtomicString& AXNodeObject::textInputType() const
1090 {
1091 Node* node = this->node();
1092 if (!isHTMLInputElement(node))
1093 return nullAtom;
1094
1095 HTMLInputElement& input = toHTMLInputElement(*node);
1096 if (input.isTextField())
1097 return input.type();
1098 return nullAtom;
1099 }
1100
1101 String AXNodeObject::ariaDescribedByAttribute() const
1102 {
1103 WillBeHeapVector<RawPtrWillBeMember<Element> > elements;
1104 elementsFromAttribute(elements, aria_describedbyAttr);
1105
1106 return accessibilityDescriptionForElements(elements);
1107 }
1108
1109 String AXNodeObject::ariaLabeledByAttribute() const
1110 {
1111 WillBeHeapVector<RawPtrWillBeMember<Element> > elements;
1112 ariaLabeledByElements(elements);
1113
1114 return accessibilityDescriptionForElements(elements);
1115 }
1116
1117 AccessibilityRole AXNodeObject::ariaRoleAttribute() const
1118 {
1119 return m_ariaRole;
1120 }
1121
1122 // When building the textUnderElement for an object, determine whether or not
1123 // we should include the inner text of this given descendant object or skip it.
1124 static bool shouldUseAccessibilityObjectInnerText(AXObject* obj)
1125 {
1126 // Consider this hypothetical example:
1127 // <div tabindex=0>
1128 // <h2>
1129 // Table of contents
1130 // </h2>
1131 // <a href="#start">Jump to start of book</a>
1132 // <ul>
1133 // <li><a href="#1">Chapter 1</a></li>
1134 // <li><a href="#1">Chapter 2</a></li>
1135 // </ul>
1136 // </div>
1137 //
1138 // The goal is to return a reasonable title for the outer container div, bec ause
1139 // it's focusable - but without making its title be the full inner text, whi ch is
1140 // quite long. As a heuristic, skip links, controls, and elements that are u sually
1141 // containers with lots of children.
1142
1143 // Skip hidden children
1144 if (obj->isInertOrAriaHidden())
1145 return false;
1146
1147 // Skip focusable children, so we don't include the text of links and contro ls.
1148 if (obj->canSetFocusAttribute())
1149 return false;
1150
1151 // Skip big container elements like lists, tables, etc.
1152 if (obj->isList() || obj->isAXTable() || obj->isTree() || obj->isCanvas())
1153 return false;
1154
1155 return true;
1156 }
1157
1158 String AXNodeObject::textUnderElement() const
1159 {
1160 Node* node = this->node();
1161 if (node && node->isTextNode())
1162 return toText(node)->wholeText();
1163
1164 StringBuilder builder;
1165 for (AXObject* child = firstChild(); child; child = child->nextSibling()) {
1166 if (!shouldUseAccessibilityObjectInnerText(child))
1167 continue;
1168
1169 if (child->isAXNodeObject()) {
1170 Vector<AccessibilityText> textOrder;
1171 toAXNodeObject(child)->alternativeText(textOrder);
1172 if (textOrder.size() > 0) {
1173 builder.append(textOrder[0].text);
1174 continue;
1175 }
1176 }
1177
1178 builder.append(child->textUnderElement());
1179 }
1180
1181 return builder.toString();
1182 }
1183
1184 String AXNodeObject::accessibilityDescription() const
1185 {
1186 // Static text should not have a description, it should only have a stringVa lue.
1187 if (roleValue() == StaticTextRole)
1188 return String();
1189
1190 String ariaDescription = ariaAccessibilityDescription();
1191 if (!ariaDescription.isEmpty())
1192 return ariaDescription;
1193
1194 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1195 // Images should use alt as long as the attribute is present, even if em pty.
1196 // Otherwise, it should fallback to other methods, like the title attrib ute.
1197 const AtomicString& alt = getAttribute(altAttr);
1198 if (!alt.isNull())
1199 return alt;
1200 }
1201
1202 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1203 // Both are used to generate what a screen reader speaks.
1204 // If this point is reached (i.e. there's no accessibilityDescription) and t here's no title(), we should fallback to using the title attribute.
1205 // The title attribute is normally used as help text (because it is a toolti p), but if there is nothing else available, this should be used (according to AR IA).
1206 if (title().isEmpty())
1207 return getAttribute(titleAttr);
1208
1209 return String();
1210 }
1211
1212 String AXNodeObject::title() const
1213 {
1214 Node* node = this->node();
1215 if (!node)
1216 return String();
1217
1218 bool isInputElement = isHTMLInputElement(*node);
1219 if (isInputElement) {
1220 HTMLInputElement& input = toHTMLInputElement(*node);
1221 if (input.isTextButton())
1222 return input.valueWithDefault();
1223 }
1224
1225 if (isInputElement || AXObject::isARIAInput(ariaRoleAttribute()) || isContro l()) {
1226 HTMLLabelElement* label = labelForElement(toElement(node));
1227 if (label && !exposesTitleUIElement())
1228 return label->innerText();
1229 }
1230
1231 // If this node isn't rendered, there's no inner text we can extract from a select element.
1232 if (!isAXRenderObject() && isHTMLSelectElement(*node))
1233 return String();
1234
1235 switch (roleValue()) {
1236 case PopUpButtonRole:
1237 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1238 if (isHTMLSelectElement(*node))
1239 return String();
1240 case ButtonRole:
1241 case ToggleButtonRole:
1242 case CheckBoxRole:
1243 case ListBoxOptionRole:
1244 case MenuButtonRole:
1245 case MenuItemRole:
1246 case MenuItemCheckBoxRole:
1247 case MenuItemRadioRole:
1248 case RadioButtonRole:
1249 case TabRole:
1250 return textUnderElement();
1251 // SVGRoots should not use the text under itself as a title. That could incl ude the text of objects like <text>.
1252 case SVGRootRole:
1253 return String();
1254 default:
1255 break;
1256 }
1257
1258 if (isHeading() || isLink())
1259 return textUnderElement();
1260
1261 // If it's focusable but it's not content editable or a known control type, then it will appear to
1262 // the user as a single atomic object, so we should use its text as the defa ult title.
1263 if (isGenericFocusableElement())
1264 return textUnderElement();
1265
1266 return String();
1267 }
1268
1269 String AXNodeObject::helpText() const
1270 {
1271 Node* node = this->node();
1272 if (!node)
1273 return String();
1274
1275 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1276 if (!ariaHelp.isEmpty())
1277 return ariaHelp;
1278
1279 String describedBy = ariaDescribedByAttribute();
1280 if (!describedBy.isEmpty())
1281 return describedBy;
1282
1283 String description = accessibilityDescription();
1284 for (Node* curr = node; curr; curr = curr->parentNode()) {
1285 if (curr->isHTMLElement()) {
1286 const AtomicString& summary = toElement(curr)->getAttribute(summaryA ttr);
1287 if (!summary.isEmpty())
1288 return summary;
1289
1290 // The title attribute should be used as help text unless it is alre ady being used as descriptive text.
1291 const AtomicString& title = toElement(curr)->getAttribute(titleAttr) ;
1292 if (!title.isEmpty() && description != title)
1293 return title;
1294 }
1295
1296 // Only take help text from an ancestor element if its a group or an unk nown role. If help was
1297 // added to those kinds of elements, it is likely it was meant for a chi ld element.
1298 AXObject* axObj = axObjectCache()->getOrCreate(curr);
1299 if (axObj) {
1300 AccessibilityRole role = axObj->roleValue();
1301 if (role != GroupRole && role != UnknownRole)
1302 break;
1303 }
1304 }
1305
1306 return String();
1307 }
1308
1309 LayoutRect AXNodeObject::elementRect() const
1310 {
1311 // First check if it has a custom rect, for example if this element is tied to a canvas path.
1312 if (!m_explicitElementRect.isEmpty())
1313 return m_explicitElementRect;
1314
1315 // FIXME: If there are a lot of elements in the canvas, it will be inefficie nt.
1316 // We can avoid the inefficient calculations by using AXComputedObjectAttrib uteCache.
1317 if (node()->parentElement()->isInCanvasSubtree()) {
1318 LayoutRect rect;
1319
1320 for (Node* child = node()->firstChild(); child; child = child->nextSibli ng()) {
1321 if (child->isHTMLElement()) {
1322 if (AXObject* obj = axObjectCache()->get(child)) {
1323 if (rect.isEmpty())
1324 rect = obj->elementRect();
1325 else
1326 rect.unite(obj->elementRect());
1327 }
1328 }
1329 }
1330
1331 if (!rect.isEmpty())
1332 return rect;
1333 }
1334
1335 // If this object doesn't have an explicit element rect or computable from i ts children,
1336 // for now, let's return the position of the ancestor that does have a posit ion,
1337 // and make it the width of that parent, and about the height of a line of t ext, so that it's clear the object is a child of the parent.
1338
1339 LayoutRect boundingBox;
1340
1341 for (AXObject* positionProvider = parentObject(); positionProvider; position Provider = positionProvider->parentObject()) {
1342 if (positionProvider->isAXRenderObject()) {
1343 LayoutRect parentRect = positionProvider->elementRect();
1344 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::m in(10.0f, parentRect.height().toFloat()))));
1345 boundingBox.setLocation(parentRect.location());
1346 break;
1347 }
1348 }
1349
1350 return boundingBox;
1351 }
1352
1353 AXObject* AXNodeObject::computeParent() const
1354 {
1355 if (!node())
1356 return 0;
1357
1358 Node* parentObj = node()->parentNode();
1359 if (parentObj)
1360 return axObjectCache()->getOrCreate(parentObj);
1361
1362 return 0;
1363 }
1364
1365 AXObject* AXNodeObject::computeParentIfExists() const
1366 {
1367 if (!node())
1368 return 0;
1369
1370 Node* parentObj = node()->parentNode();
1371 if (parentObj)
1372 return axObjectCache()->get(parentObj);
1373
1374 return 0;
1375 }
1376
1377 AXObject* AXNodeObject::firstChild() const
1378 {
1379 if (!node())
1380 return 0;
1381
1382 Node* firstChild = node()->firstChild();
1383
1384 if (!firstChild)
1385 return 0;
1386
1387 return axObjectCache()->getOrCreate(firstChild);
1388 }
1389
1390 AXObject* AXNodeObject::nextSibling() const
1391 {
1392 if (!node())
1393 return 0;
1394
1395 Node* nextSibling = node()->nextSibling();
1396 if (!nextSibling)
1397 return 0;
1398
1399 return axObjectCache()->getOrCreate(nextSibling);
1400 }
1401
1402 void AXNodeObject::addChildren()
1403 {
1404 // If the need to add more children in addition to existing children arises,
1405 // childrenChanged should have been called, leaving the object with no child ren.
1406 ASSERT(!m_haveChildren);
1407
1408 if (!m_node)
1409 return;
1410
1411 m_haveChildren = true;
1412
1413 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
1414 if (renderer() && !isHTMLCanvasElement(*m_node))
1415 return;
1416
1417 for (Node* child = m_node->firstChild(); child; child = child->nextSibling() )
1418 addChild(axObjectCache()->getOrCreate(child));
1419
1420 for (unsigned i = 0; i < m_children.size(); ++i)
1421 m_children[i].get()->setParent(this);
1422 }
1423
1424 void AXNodeObject::addChild(AXObject* child)
1425 {
1426 insertChild(child, m_children.size());
1427 }
1428
1429 void AXNodeObject::insertChild(AXObject* child, unsigned index)
1430 {
1431 if (!child)
1432 return;
1433
1434 // If the parent is asking for this child's children, then either it's the f irst time (and clearing is a no-op),
1435 // or its visibility has changed. In the latter case, this child may have a stale child cached.
1436 // This can prevent aria-hidden changes from working correctly. Hence, whene ver a parent is getting children, ensure data is not stale.
1437 child->clearChildren();
1438
1439 if (child->accessibilityIsIgnored()) {
1440 AccessibilityChildrenVector children = child->children();
1441 size_t length = children.size();
1442 for (size_t i = 0; i < length; ++i)
1443 m_children.insert(index + i, children[i]);
1444 } else {
1445 ASSERT(child->parentObject() == this);
1446 m_children.insert(index, child);
1447 }
1448 }
1449
1450 bool AXNodeObject::canHaveChildren() const
1451 {
1452 // If this is an AXRenderObject, then it's okay if this object
1453 // doesn't have a node - there are some renderers that don't have associated
1454 // nodes, like scroll areas and css-generated text.
1455 if (!node() && !isAXRenderObject())
1456 return false;
1457
1458 // Elements that should not have children
1459 switch (roleValue()) {
1460 case ImageRole:
1461 case ButtonRole:
1462 case PopUpButtonRole:
1463 case CheckBoxRole:
1464 case RadioButtonRole:
1465 case TabRole:
1466 case ToggleButtonRole:
1467 case ListBoxOptionRole:
1468 case ScrollBarRole:
1469 return false;
1470 case StaticTextRole:
1471 if (!axObjectCache()->inlineTextBoxAccessibilityEnabled())
1472 return false;
1473 default:
1474 return true;
1475 }
1476 }
1477
1478 Element* AXNodeObject::actionElement() const
1479 {
1480 Node* node = this->node();
1481 if (!node)
1482 return 0;
1483
1484 if (isHTMLInputElement(*node)) {
1485 HTMLInputElement& input = toHTMLInputElement(*node);
1486 if (!input.isDisabledFormControl() && (isCheckboxOrRadio() || input.isTe xtButton()))
1487 return &input;
1488 } else if (isHTMLButtonElement(*node)) {
1489 return toElement(node);
1490 }
1491
1492 if (isFileUploadButton())
1493 return toElement(node);
1494
1495 if (AXObject::isARIAInput(ariaRoleAttribute()))
1496 return toElement(node);
1497
1498 if (isImageButton())
1499 return toElement(node);
1500
1501 if (isHTMLSelectElement(*node))
1502 return toElement(node);
1503
1504 switch (roleValue()) {
1505 case ButtonRole:
1506 case PopUpButtonRole:
1507 case ToggleButtonRole:
1508 case TabRole:
1509 case MenuItemRole:
1510 case MenuItemCheckBoxRole:
1511 case MenuItemRadioRole:
1512 case ListItemRole:
1513 return toElement(node);
1514 default:
1515 break;
1516 }
1517
1518 Element* elt = anchorElement();
1519 if (!elt)
1520 elt = mouseButtonListener();
1521 return elt;
1522 }
1523
1524 Element* AXNodeObject::anchorElement() const
1525 {
1526 Node* node = this->node();
1527 if (!node)
1528 return 0;
1529
1530 AXObjectCacheImpl* cache = axObjectCache();
1531
1532 // search up the DOM tree for an anchor element
1533 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElem ent
1534 for ( ; node; node = node->parentNode()) {
1535 if (isHTMLAnchorElement(*node) || (node->renderer() && cache->getOrCreat e(node->renderer())->isAnchor()))
1536 return toElement(node);
1537 }
1538
1539 return 0;
1540 }
1541
1542 Document* AXNodeObject::document() const
1543 {
1544 if (!node())
1545 return 0;
1546 return &node()->document();
1547 }
1548
1549 void AXNodeObject::setNode(Node* node)
1550 {
1551 m_node = node;
1552 }
1553
1554 AXObject* AXNodeObject::correspondingControlForLabelElement() const
1555 {
1556 HTMLLabelElement* labelElement = labelElementContainer();
1557 if (!labelElement)
1558 return 0;
1559
1560 HTMLElement* correspondingControl = labelElement->control();
1561 if (!correspondingControl)
1562 return 0;
1563
1564 // Make sure the corresponding control isn't a descendant of this label
1565 // that's in the middle of being destroyed.
1566 if (correspondingControl->renderer() && !correspondingControl->renderer()->p arent())
1567 return 0;
1568
1569 return axObjectCache()->getOrCreate(correspondingControl);
1570 }
1571
1572 HTMLLabelElement* AXNodeObject::labelElementContainer() const
1573 {
1574 if (!node())
1575 return 0;
1576
1577 // the control element should not be considered part of the label
1578 if (isControl())
1579 return 0;
1580
1581 // find if this has a ancestor that is a label
1582 return Traversal<HTMLLabelElement>::firstAncestorOrSelf(*node());
1583 }
1584
1585 void AXNodeObject::setFocused(bool on)
1586 {
1587 if (!canSetFocusAttribute())
1588 return;
1589
1590 Document* document = this->document();
1591 if (!on) {
1592 document->setFocusedElement(nullptr);
1593 } else {
1594 Node* node = this->node();
1595 if (node && node->isElementNode()) {
1596 // If this node is already the currently focused node, then calling focus() won't do anything.
1597 // That is a problem when focus is removed from the webpage to chrom e, and then returns.
1598 // In these cases, we need to do what keyboard and mouse focus do, w hich is reset focus first.
1599 if (document->focusedElement() == node)
1600 document->setFocusedElement(nullptr);
1601
1602 toElement(node)->focus();
1603 } else {
1604 document->setFocusedElement(nullptr);
1605 }
1606 }
1607 }
1608
1609 void AXNodeObject::increment()
1610 {
1611 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
1612 alterSliderValue(true);
1613 }
1614
1615 void AXNodeObject::decrement()
1616 {
1617 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
1618 alterSliderValue(false);
1619 }
1620
1621 void AXNodeObject::childrenChanged()
1622 {
1623 // This method is meant as a quick way of marking a portion of the accessibi lity tree dirty.
1624 if (!node() && !renderer())
1625 return;
1626
1627 axObjectCache()->postNotification(this, document(), AXObjectCacheImpl::AXChi ldrenChanged, true);
1628
1629 // Go up the accessibility parent chain, but only if the element already exi sts. This method is
1630 // called during render layouts, minimal work should be done.
1631 // If AX elements are created now, they could interrogate the render tree wh ile it's in a funky state.
1632 // At the same time, process ARIA live region changes.
1633 for (AXObject* parent = this; parent; parent = parent->parentObjectIfExists( )) {
1634 parent->setNeedsToUpdateChildren();
1635
1636 // These notifications always need to be sent because screenreaders are reliant on them to perform.
1637 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
1638
1639 // If this element supports ARIA live regions, then notify the AT of cha nges.
1640 if (parent->isLiveRegion())
1641 axObjectCache()->postNotification(parent, parent->document(), AXObje ctCacheImpl::AXLiveRegionChanged, true);
1642
1643 // If this element is an ARIA text box or content editable, post a "valu e changed" notification on it
1644 // so that it behaves just like a native input element or textarea.
1645 if (isNonNativeTextControl())
1646 axObjectCache()->postNotification(parent, parent->document(), AXObje ctCacheImpl::AXValueChanged, true);
1647 }
1648 }
1649
1650 void AXNodeObject::selectionChanged()
1651 {
1652 // Post the selected text changed event on the first ancestor that's
1653 // focused (to handle form controls, ARIA text boxes and contentEditable),
1654 // or the web area if the selection is just in the document somewhere.
1655 if (isFocused() || isWebArea())
1656 axObjectCache()->postNotification(this, document(), AXObjectCacheImpl::A XSelectedTextChanged, true);
1657 else
1658 AXObject::selectionChanged(); // Calls selectionChanged on parent.
1659 }
1660
1661 void AXNodeObject::textChanged()
1662 {
1663 // If this element supports ARIA live regions, or is part of a region with a n ARIA editable role,
1664 // then notify the AT of changes.
1665 AXObjectCacheImpl* cache = axObjectCache();
1666 for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentN ode()) {
1667 AXObject* parent = cache->get(parentNode);
1668 if (!parent)
1669 continue;
1670
1671 if (parent->isLiveRegion())
1672 cache->postNotification(parentNode, AXObjectCacheImpl::AXLiveRegionC hanged, true);
1673
1674 // If this element is an ARIA text box or content editable, post a "valu e changed" notification on it
1675 // so that it behaves just like a native input element or textarea.
1676 if (parent->isNonNativeTextControl())
1677 cache->postNotification(parentNode, AXObjectCacheImpl::AXValueChange d, true);
1678 }
1679 }
1680
1681 void AXNodeObject::updateAccessibilityRole()
1682 {
1683 bool ignoredStatus = accessibilityIsIgnored();
1684 m_role = determineAccessibilityRole();
1685
1686 // The AX hierarchy only needs to be updated if the ignored status of an ele ment has changed.
1687 if (ignoredStatus != accessibilityIsIgnored())
1688 childrenChanged();
1689 }
1690
1691 String AXNodeObject::alternativeTextForWebArea() const
1692 {
1693 // The WebArea description should follow this order:
1694 // aria-label on the <html>
1695 // title on the <html>
1696 // <title> inside the <head> (of it was set through JS)
1697 // name on the <html>
1698 // For iframes:
1699 // aria-label on the <iframe>
1700 // title on the <iframe>
1701 // name on the <iframe>
1702
1703 Document* document = this->document();
1704 if (!document)
1705 return String();
1706
1707 // Check if the HTML element has an aria-label for the webpage.
1708 if (Element* documentElement = document->documentElement()) {
1709 const AtomicString& ariaLabel = documentElement->getAttribute(aria_label Attr);
1710 if (!ariaLabel.isEmpty())
1711 return ariaLabel;
1712 }
1713
1714 if (HTMLFrameOwnerElement* owner = document->ownerElement()) {
1715 if (isHTMLFrameElementBase(*owner)) {
1716 const AtomicString& title = owner->getAttribute(titleAttr);
1717 if (!title.isEmpty())
1718 return title;
1719 }
1720 return owner->getNameAttribute();
1721 }
1722
1723 String documentTitle = document->title();
1724 if (!documentTitle.isEmpty())
1725 return documentTitle;
1726
1727 if (HTMLElement* body = document->body())
1728 return body->getNameAttribute();
1729
1730 return String();
1731 }
1732
1733 void AXNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1734 {
1735 if (isWebArea()) {
1736 String webAreaText = alternativeTextForWebArea();
1737 if (!webAreaText.isEmpty())
1738 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1739 return;
1740 }
1741
1742 ariaLabeledByText(textOrder);
1743
1744 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1745 if (!ariaLabel.isEmpty())
1746 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1747
1748 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1749 // Images should use alt as long as the attribute is present, even if em pty.
1750 // Otherwise, it should fallback to other methods, like the title attrib ute.
1751 const AtomicString& alt = getAttribute(altAttr);
1752 if (!alt.isNull())
1753 textOrder.append(AccessibilityText(alt, AlternativeText));
1754 }
1755 }
1756
1757 void AXNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1758 {
1759 String ariaLabeledBy = ariaLabeledByAttribute();
1760 if (!ariaLabeledBy.isEmpty()) {
1761 WillBeHeapVector<RawPtrWillBeMember<Element> > elements;
1762 ariaLabeledByElements(elements);
1763
1764 unsigned length = elements.size();
1765 for (unsigned k = 0; k < length; k++) {
1766 RefPtr<AXObject> axElement = axObjectCache()->getOrCreate(elements[k ]);
1767 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, a xElement));
1768 }
1769 }
1770 }
1771
1772 void AXNodeObject::changeValueByPercent(float percentChange)
1773 {
1774 float range = maxValueForRange() - minValueForRange();
1775 float value = valueForRange();
1776
1777 value += range * (percentChange / 100);
1778 setValue(String::number(value));
1779
1780 axObjectCache()->postNotification(node(), AXObjectCacheImpl::AXValueChanged, true);
1781 }
1782
1783 } // namespace blink
OLDNEW
« no previous file with comments | « Source/core/accessibility/AXNodeObject.h ('k') | Source/core/accessibility/AXObject.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698