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