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 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 64 PassRefPtr<AXNodeObject> AXNodeObject::create(Node* node) 65 { 66 return adoptRef(new AXNodeObject(node)); 67 } 68 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. 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 96 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 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 124 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 138 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 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 157 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 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 247 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 273 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. 300 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 309 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 319 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 354 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 368 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 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 396 Element* AXNodeObject::menuItemElementForMenu() const 397 { 398 if (ariaRoleAttribute() != MenuRole) 399 return 0; 400 401 return siblingWithAriaRole("menuitem", node()); 402 } 403 404 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 426 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 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 463 void AXNodeObject::detach() 464 { 465 clearChildren(); 466 AXObject::detach(); 467 m_node = 0; 468 } 469 470 bool AXNodeObject::isAnchor() const 471 { 472 return !isNativeImage() && isLink(); 473 } 474 475 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 485 bool AXNodeObject::isEmbeddedObject() const 486 { 487 return isHTMLPlugInElement(node()); 488 } 489 490 bool AXNodeObject::isFieldset() const 491 { 492 return isHTMLFieldSetElement(node()); 493 } 494 495 bool AXNodeObject::isHeading() const 496 { 497 return roleValue() == HeadingRole; 498 } 499 500 bool AXNodeObject::isHovered() const 501 { 502 Node* node = this->node(); 503 if (!node) 504 return false; 505 506 return node->hovered(); 507 } 508 509 bool AXNodeObject::isImage() const 510 { 511 return roleValue() == ImageRole; 512 } 513 514 bool AXNodeObject::isImageButton() const 515 { 516 return isNativeImage() && isButton(); 517 } 518 519 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 528 bool AXNodeObject::isLink() const 529 { 530 return roleValue() == LinkRole; 531 } 532 533 bool AXNodeObject::isMenu() const 534 { 535 return roleValue() == MenuRole; 536 } 537 538 bool AXNodeObject::isMenuButton() const 539 { 540 return roleValue() == MenuButtonRole; 541 } 542 543 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 554 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 564 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 582 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 597 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 611 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 623 bool AXNodeObject::isProgressIndicator() const 624 { 625 return roleValue() == ProgressIndicatorRole; 626 } 627 628 bool AXNodeObject::isSlider() const 629 { 630 return roleValue() == SliderRole; 631 } 632 633 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 655 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 669 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 681 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 690 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 709 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 727 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 739 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 760 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 776 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 788 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 815 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 850 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 878 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 900 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 915 AccessibilityButtonState AXNodeObject::checkboxOrRadioValue() const 916 { 917 if (isNativeCheckboxOrRadio()) 918 return isChecked() ? ButtonStateOn : ButtonStateOff; 919 920 return AXObject::checkboxOrRadioValue(); 921 } 922 923 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 949 String AXNodeObject::valueDescription() const 950 { 951 if (!supportsRangeValue()) 952 return String(); 953 954 return getAttribute(aria_valuetextAttr).string(); 955 } 956 957 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 971 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 985 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 999 float AXNodeObject::stepValueForRange() const 1000 { 1001 return getAttribute(stepAttr).toFloat(); 1002 } 1003 1004 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 1044 String AXNodeObject::ariaDescribedByAttribute() const 1045 { 1046 WillBeHeapVector<RawPtrWillBeMember<Element> > elements; 1047 elementsFromAttribute(elements, aria_describedbyAttr); 1048 1049 return accessibilityDescriptionForElements(elements); 1050 } 1051 1052 1053 String AXNodeObject::ariaLabeledByAttribute() const 1054 { 1055 WillBeHeapVector<RawPtrWillBeMember<Element> > elements; 1056 ariaLabeledByElements(elements); 1057 1058 return accessibilityDescriptionForElements(elements); 1059 } 1060 1061 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. 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 1102 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 1128 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 1156 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 1211 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 1251 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 1295 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 1307 AXObject* AXNodeObject::parentObjectIfExists() const 1308 { 1309 return parentObject(); 1310 } 1311 1312 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 1325 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 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 1356 void AXNodeObject::addChild(AXObject* child) 1357 { 1358 insertChild(child, m_children.size()); 1359 } 1360 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 1382 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 1410 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 1454 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 1472 Document* AXNodeObject::document() const 1473 { 1474 if (!node()) 1475 return 0; 1476 return &node()->document(); 1477 } 1478 1479 void AXNodeObject::setNode(Node* node) 1480 { 1481 m_node = node; 1482 } 1483 1484 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 1502 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 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 1539 void AXNodeObject::increment() 1540 { 1541 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); 1542 alterSliderValue(true); 1543 } 1544 1545 void AXNodeObject::decrement() 1546 { 1547 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); 1548 alterSliderValue(false); 1549 } 1550 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 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 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 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 1621 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 1663 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 1687 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 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