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