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