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