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 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 "AccessibilityNodeObject.h" 31 32#include "AXObjectCache.h" 33#include "AccessibilityImageMapLink.h" 34#include "AccessibilityListBox.h" 35#include "AccessibilitySpinButton.h" 36#include "AccessibilityTable.h" 37#include "ElementIterator.h" 38#include "EventNames.h" 39#include "FloatRect.h" 40#include "Frame.h" 41#include "FrameLoader.h" 42#include "FrameSelection.h" 43#include "FrameView.h" 44#include "HTMLAreaElement.h" 45#include "HTMLFieldSetElement.h" 46#include "HTMLFormElement.h" 47#include "HTMLFrameElementBase.h" 48#include "HTMLImageElement.h" 49#include "HTMLInputElement.h" 50#include "HTMLLabelElement.h" 51#include "HTMLLegendElement.h" 52#include "HTMLMapElement.h" 53#include "HTMLNames.h" 54#include "HTMLOptGroupElement.h" 55#include "HTMLOptionElement.h" 56#include "HTMLOptionsCollection.h" 57#include "HTMLParserIdioms.h" 58#include "HTMLPlugInImageElement.h" 59#include "HTMLSelectElement.h" 60#include "HTMLTextAreaElement.h" 61#include "HTMLTextFormControlElement.h" 62#include "HitTestRequest.h" 63#include "HitTestResult.h" 64#include "LabelableElement.h" 65#include "LocalizedStrings.h" 66#include "MathMLNames.h" 67#include "NodeList.h" 68#include "NodeTraversal.h" 69#include "Page.h" 70#include "ProgressTracker.h" 71#include "RenderImage.h" 72#include "RenderView.h" 73#include "SVGElement.h" 74#include "SVGNames.h" 75#include "Text.h" 76#include "TextControlInnerElements.h" 77#include "UserGestureIndicator.h" 78#include "VisibleUnits.h" 79#include "Widget.h" 80#include "htmlediting.h" 81#include <wtf/StdLibExtras.h> 82#include <wtf/text/StringBuilder.h> 83#include <wtf/unicode/CharacterNames.h> 84 85namespace WebCore { 86 87using namespace HTMLNames; 88 89static String accessibleNameForNode(Node*); 90 91AccessibilityNodeObject::AccessibilityNodeObject(Node* node) 92 : AccessibilityObject() 93 , m_ariaRole(UnknownRole) 94 , m_childrenDirty(false) 95 , m_roleForMSAA(UnknownRole) 96#ifndef NDEBUG 97 , m_initialized(false) 98#endif 99 , m_node(node) 100{ 101} 102 103AccessibilityNodeObject::~AccessibilityNodeObject() 104{ 105 ASSERT(isDetached()); 106} 107 108void AccessibilityNodeObject::init() 109{ 110#ifndef NDEBUG 111 ASSERT(!m_initialized); 112 m_initialized = true; 113#endif 114 m_role = determineAccessibilityRole(); 115} 116 117PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node) 118{ 119 return adoptRef(new AccessibilityNodeObject(node)); 120} 121 122void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) 123{ 124 // AccessibilityObject calls clearChildren. 125 AccessibilityObject::detach(detachmentType, cache); 126 m_node = 0; 127} 128 129void AccessibilityNodeObject::childrenChanged() 130{ 131 // This method is meant as a quick way of marking a portion of the accessibility tree dirty. 132 if (!node() && !renderer()) 133 return; 134 135 AXObjectCache* cache = axObjectCache(); 136 if (!cache) 137 return; 138 cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged); 139 140 // Go up the accessibility parent chain, but only if the element already exists. This method is 141 // called during render layouts, minimal work should be done. 142 // If AX elements are created now, they could interrogate the render tree while it's in a funky state. 143 // At the same time, process ARIA live region changes. 144 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) { 145 parent->setNeedsToUpdateChildren(); 146 147 // These notifications always need to be sent because screenreaders are reliant on them to perform. 148 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update. 149 150 // If this element supports ARIA live regions, then notify the AT of changes. 151 if (parent->supportsARIALiveRegion()) 152 cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged); 153 154 // If this element is an ARIA text control, notify the AT of changes. 155 if ((parent->isARIATextControl() || parent->hasContentEditableAttributeSet()) && !parent->isNativeTextControl()) 156 cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged); 157 } 158} 159 160void AccessibilityNodeObject::updateAccessibilityRole() 161{ 162 bool ignoredStatus = accessibilityIsIgnored(); 163 m_role = determineAccessibilityRole(); 164 165 // The AX hierarchy only needs to be updated if the ignored status of an element has changed. 166 if (ignoredStatus != accessibilityIsIgnored()) 167 childrenChanged(); 168} 169 170AccessibilityObject* AccessibilityNodeObject::firstChild() const 171{ 172 if (!node()) 173 return 0; 174 175 Node* firstChild = node()->firstChild(); 176 177 if (!firstChild) 178 return 0; 179 180 return axObjectCache()->getOrCreate(firstChild); 181} 182 183AccessibilityObject* AccessibilityNodeObject::lastChild() const 184{ 185 if (!node()) 186 return 0; 187 188 Node* lastChild = node()->lastChild(); 189 if (!lastChild) 190 return 0; 191 192 return axObjectCache()->getOrCreate(lastChild); 193} 194 195AccessibilityObject* AccessibilityNodeObject::previousSibling() const 196{ 197 if (!node()) 198 return 0; 199 200 Node* previousSibling = node()->previousSibling(); 201 if (!previousSibling) 202 return 0; 203 204 return axObjectCache()->getOrCreate(previousSibling); 205} 206 207AccessibilityObject* AccessibilityNodeObject::nextSibling() const 208{ 209 if (!node()) 210 return 0; 211 212 Node* nextSibling = node()->nextSibling(); 213 if (!nextSibling) 214 return 0; 215 216 return axObjectCache()->getOrCreate(nextSibling); 217} 218 219AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const 220{ 221 return parentObject(); 222} 223 224AccessibilityObject* AccessibilityNodeObject::parentObject() const 225{ 226 if (!node()) 227 return 0; 228 229 Node* parentObj = node()->parentNode(); 230 if (!parentObj) 231 return nullptr; 232 233 if (AXObjectCache* cache = axObjectCache()) 234 return cache->getOrCreate(parentObj); 235 236 return 0; 237} 238 239LayoutRect AccessibilityNodeObject::elementRect() const 240{ 241 return boundingBoxRect(); 242} 243 244LayoutRect AccessibilityNodeObject::boundingBoxRect() const 245{ 246 // AccessibilityNodeObjects have no mechanism yet to return a size or position. 247 // For now, let's return the position of the ancestor that does have a position, 248 // 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. 249 250 LayoutRect boundingBox; 251 252 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) { 253 if (positionProvider->isAccessibilityRenderObject()) { 254 LayoutRect parentRect = positionProvider->elementRect(); 255 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat())))); 256 boundingBox.setLocation(parentRect.location()); 257 break; 258 } 259 } 260 261 return boundingBox; 262} 263 264void AccessibilityNodeObject::setNode(Node* node) 265{ 266 m_node = node; 267} 268 269Document* AccessibilityNodeObject::document() const 270{ 271 if (!node()) 272 return 0; 273 return &node()->document(); 274} 275 276AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole() 277{ 278 if (!node()) 279 return UnknownRole; 280 281 m_ariaRole = determineAriaRoleAttribute(); 282 283 AccessibilityRole ariaRole = ariaRoleAttribute(); 284 if (ariaRole != UnknownRole) 285 return ariaRole; 286 287 if (node()->isLink()) 288 return WebCoreLinkRole; 289 if (node()->isTextNode()) 290 return StaticTextRole; 291 if (node()->hasTagName(buttonTag)) 292 return buttonRoleType(); 293 if (isHTMLInputElement(node())) { 294 HTMLInputElement* input = toHTMLInputElement(node()); 295 if (input->isCheckbox()) 296 return CheckBoxRole; 297 if (input->isRadioButton()) 298 return RadioButtonRole; 299 if (input->isTextButton()) 300 return buttonRoleType(); 301 if (input->isRangeControl()) 302 return SliderRole; 303 304#if ENABLE(INPUT_TYPE_COLOR) 305 const AtomicString& type = input->getAttribute(typeAttr); 306 if (equalIgnoringCase(type, "color")) 307 return ColorWellRole; 308#endif 309 310 return TextFieldRole; 311 } 312 if (node()->hasTagName(selectTag)) { 313 HTMLSelectElement* selectElement = toHTMLSelectElement(node()); 314 return selectElement->multiple() ? ListBoxRole : PopUpButtonRole; 315 } 316 if (isHTMLTextAreaElement(node())) 317 return TextAreaRole; 318 if (headingLevel()) 319 return HeadingRole; 320 if (node()->hasTagName(divTag)) 321 return DivRole; 322 if (node()->hasTagName(pTag)) 323 return ParagraphRole; 324 if (isHTMLLabelElement(node())) 325 return LabelRole; 326 if (node()->isElementNode() && toElement(node())->isFocusable()) 327 return GroupRole; 328 329 return UnknownRole; 330} 331 332void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index) 333{ 334 if (!child) 335 return; 336 337 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op), 338 // or its visibility has changed. In the latter case, this child may have a stale child cached. 339 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale. 340 child->clearChildren(); 341 342 if (child->accessibilityIsIgnored()) { 343 const auto& children = child->children(); 344 size_t length = children.size(); 345 for (size_t i = 0; i < length; ++i) 346 m_children.insert(index + i, children[i]); 347 } else { 348 ASSERT(child->parentObject() == this); 349 m_children.insert(index, child); 350 } 351} 352 353void AccessibilityNodeObject::addChild(AccessibilityObject* child) 354{ 355 insertChild(child, m_children.size()); 356} 357 358void AccessibilityNodeObject::addChildren() 359{ 360 // If the need to add more children in addition to existing children arises, 361 // childrenChanged should have been called, leaving the object with no children. 362 ASSERT(!m_haveChildren); 363 364 if (!m_node) 365 return; 366 367 m_haveChildren = true; 368 369 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas. 370 if (renderer() && !m_node->hasTagName(canvasTag)) 371 return; 372 373 for (Node* child = m_node->firstChild(); child; child = child->nextSibling()) 374 addChild(axObjectCache()->getOrCreate(child)); 375} 376 377bool AccessibilityNodeObject::canHaveChildren() const 378{ 379 // If this is an AccessibilityRenderObject, then it's okay if this object 380 // doesn't have a node - there are some renderers that don't have associated 381 // nodes, like scroll areas and css-generated text. 382 if (!node() && !isAccessibilityRenderObject()) 383 return false; 384 385 // When <noscript> is not being used (its renderer() == 0), ignore its children. 386 if (node() && !renderer() && node()->hasTagName(noscriptTag)) 387 return false; 388 389 // Elements that should not have children 390 switch (roleValue()) { 391 case ImageRole: 392 case ButtonRole: 393 case PopUpButtonRole: 394 case CheckBoxRole: 395 case RadioButtonRole: 396 case TabRole: 397 case ToggleButtonRole: 398 case StaticTextRole: 399 case ListBoxOptionRole: 400 case ScrollBarRole: 401 case ProgressIndicatorRole: 402 return false; 403 case LegendRole: 404 if (Element* element = this->element()) 405 return !ancestorsOfType<HTMLFieldSetElement>(*element).first(); 406 FALLTHROUGH; 407 default: 408 return true; 409 } 410} 411 412bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const 413{ 414#ifndef NDEBUG 415 // Double-check that an AccessibilityObject is never accessed before 416 // it's been initialized. 417 ASSERT(m_initialized); 418#endif 419 420 // Handle non-rendered text that is exposed through aria-hidden=false. 421 if (m_node && m_node->isTextNode() && !renderer()) { 422 // Fallback content in iframe nodes should be ignored. 423 if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer()) 424 return true; 425 426 // Whitespace only text elements should be ignored when they have no renderer. 427 String string = stringValue().stripWhiteSpace().simplifyWhiteSpace(); 428 if (!string.length()) 429 return true; 430 } 431 432 AccessibilityObjectInclusion decision = defaultObjectInclusion(); 433 if (decision == IncludeObject) 434 return false; 435 if (decision == IgnoreObject) 436 return true; 437 // If this element is within a parent that cannot have children, it should not be exposed. 438 if (isDescendantOfBarrenParent()) 439 return true; 440 441 return m_role == UnknownRole; 442} 443 444bool AccessibilityNodeObject::canvasHasFallbackContent() const 445{ 446 Node* node = this->node(); 447 if (!node || !node->hasTagName(canvasTag)) 448 return false; 449 Element& canvasElement = toElement(*node); 450 // If it has any children that are elements, we'll assume it might be fallback 451 // content. If it has no children or its only children are not elements 452 // (e.g. just text nodes), it doesn't have fallback content. 453 return childrenOfType<Element>(canvasElement).first(); 454} 455 456bool AccessibilityNodeObject::isImageButton() const 457{ 458 return isNativeImage() && isButton(); 459} 460 461bool AccessibilityNodeObject::isAnchor() const 462{ 463 return !isNativeImage() && isLink(); 464} 465 466bool AccessibilityNodeObject::isNativeTextControl() const 467{ 468 Node* node = this->node(); 469 if (!node) 470 return false; 471 472 if (isHTMLTextAreaElement(node)) 473 return true; 474 475 if (isHTMLInputElement(node)) { 476 HTMLInputElement* input = toHTMLInputElement(node); 477 return input->isText() || input->isNumberField(); 478 } 479 480 return false; 481} 482 483bool AccessibilityNodeObject::isSearchField() const 484{ 485 Node* node = this->node(); 486 if (!node) 487 return false; 488 489 HTMLInputElement* inputElement = node->toInputElement(); 490 if (!inputElement) 491 return false; 492 493 if (inputElement->isSearchField()) 494 return true; 495 496 // Some websites don't label their search fields as such. However, they will 497 // use the word "search" in either the form or input type. This won't catch every case, 498 // but it will catch google.com for example. 499 500 // Check the node name of the input type, sometimes it's "search". 501 const AtomicString& nameAttribute = getAttribute(nameAttr); 502 if (nameAttribute.contains("search", false)) 503 return true; 504 505 // Check the form action and the name, which will sometimes be "search". 506 HTMLFormElement* form = inputElement->form(); 507 if (form && (form->name().contains("search", false) || form->action().contains("search", false))) 508 return true; 509 510 return false; 511} 512 513bool AccessibilityNodeObject::isNativeImage() const 514{ 515 Node* node = this->node(); 516 if (!node) 517 return false; 518 519 if (isHTMLImageElement(node)) 520 return true; 521 522 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag)) 523 return true; 524 525 if (isHTMLInputElement(node)) { 526 HTMLInputElement* input = toHTMLInputElement(node); 527 return input->isImageButton(); 528 } 529 530 return false; 531} 532 533bool AccessibilityNodeObject::isImage() const 534{ 535 return roleValue() == ImageRole; 536} 537 538bool AccessibilityNodeObject::isPasswordField() const 539{ 540 Node* node = this->node(); 541 if (!node || !node->isHTMLElement()) 542 return false; 543 544 if (ariaRoleAttribute() != UnknownRole) 545 return false; 546 547 HTMLInputElement* inputElement = node->toInputElement(); 548 if (!inputElement) 549 return false; 550 551 return inputElement->isPasswordField(); 552} 553 554bool AccessibilityNodeObject::isInputImage() const 555{ 556 Node* node = this->node(); 557 if (!node) 558 return false; 559 560 if (roleValue() == ButtonRole && isHTMLInputElement(node)) { 561 HTMLInputElement* input = toHTMLInputElement(node); 562 return input->isImageButton(); 563 } 564 565 return false; 566} 567 568bool AccessibilityNodeObject::isProgressIndicator() const 569{ 570 return roleValue() == ProgressIndicatorRole; 571} 572 573bool AccessibilityNodeObject::isSlider() const 574{ 575 return roleValue() == SliderRole; 576} 577 578bool AccessibilityNodeObject::isMenuRelated() const 579{ 580 switch (roleValue()) { 581 case MenuRole: 582 case MenuBarRole: 583 case MenuButtonRole: 584 case MenuItemRole: 585 case MenuItemCheckboxRole: 586 case MenuItemRadioRole: 587 return true; 588 default: 589 return false; 590 } 591} 592 593bool AccessibilityNodeObject::isMenu() const 594{ 595 return roleValue() == MenuRole; 596} 597 598bool AccessibilityNodeObject::isMenuBar() const 599{ 600 return roleValue() == MenuBarRole; 601} 602 603bool AccessibilityNodeObject::isMenuButton() const 604{ 605 return roleValue() == MenuButtonRole; 606} 607 608bool AccessibilityNodeObject::isMenuItem() const 609{ 610 switch (roleValue()) { 611 case MenuItemRole: 612 case MenuItemRadioRole: 613 case MenuItemCheckboxRole: 614 return true; 615 default: 616 return false; 617 } 618} 619 620bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const 621{ 622 Node* node = this->node(); 623 if (!node) 624 return false; 625 626 HTMLInputElement* input = node->toInputElement(); 627 if (input) 628 return input->isCheckbox() || input->isRadioButton(); 629 630 return false; 631} 632 633bool AccessibilityNodeObject::isEnabled() const 634{ 635 // ARIA says that the disabled status applies to the current element and all descendant elements. 636 for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) { 637 const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr); 638 if (equalIgnoringCase(disabledStatus, "true")) 639 return false; 640 if (equalIgnoringCase(disabledStatus, "false")) 641 break; 642 } 643 644 if (roleValue() == HorizontalRuleRole) 645 return false; 646 647 Node* node = this->node(); 648 if (!node || !node->isElementNode()) 649 return true; 650 651 return !toElement(node)->isDisabledFormControl(); 652} 653 654bool AccessibilityNodeObject::isIndeterminate() const 655{ 656 Node* node = this->node(); 657 if (!node) 658 return false; 659 660 HTMLInputElement* inputElement = node->toInputElement(); 661 if (!inputElement) 662 return false; 663 664 return inputElement->shouldAppearIndeterminate(); 665} 666 667bool AccessibilityNodeObject::isPressed() const 668{ 669 if (!isButton()) 670 return false; 671 672 Node* node = this->node(); 673 if (!node) 674 return false; 675 676 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active() 677 if (ariaRoleAttribute() == ButtonRole) { 678 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true")) 679 return true; 680 return false; 681 } 682 683 if (!node->isElementNode()) 684 return false; 685 return toElement(node)->active(); 686} 687 688bool AccessibilityNodeObject::isChecked() const 689{ 690 Node* node = this->node(); 691 if (!node) 692 return false; 693 694 // First test for native checkedness semantics 695 HTMLInputElement* inputElement = node->toInputElement(); 696 if (inputElement) 697 return inputElement->shouldAppearChecked(); 698 699 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute 700 bool validRole = false; 701 switch (ariaRoleAttribute()) { 702 case RadioButtonRole: 703 case CheckBoxRole: 704 case MenuItemRole: 705 case MenuItemCheckboxRole: 706 case MenuItemRadioRole: 707 validRole = true; 708 break; 709 default: 710 break; 711 } 712 713 if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true")) 714 return true; 715 716 return false; 717} 718 719bool AccessibilityNodeObject::isHovered() const 720{ 721 Node* node = this->node(); 722 if (!node) 723 return false; 724 725 return node->isElementNode() && toElement(node)->hovered(); 726} 727 728bool AccessibilityNodeObject::isMultiSelectable() const 729{ 730 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr); 731 if (equalIgnoringCase(ariaMultiSelectable, "true")) 732 return true; 733 if (equalIgnoringCase(ariaMultiSelectable, "false")) 734 return false; 735 736 return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple(); 737} 738 739bool AccessibilityNodeObject::isReadOnly() const 740{ 741 Node* node = this->node(); 742 if (!node) 743 return true; 744 745 if (isHTMLTextAreaElement(node)) 746 return toHTMLFormControlElement(node)->isReadOnly(); 747 748 if (isHTMLInputElement(node)) { 749 HTMLInputElement* input = toHTMLInputElement(node); 750 if (input->isTextField()) 751 return input->isReadOnly(); 752 } 753 754 return !node->hasEditableStyle(); 755} 756 757bool AccessibilityNodeObject::isRequired() const 758{ 759 // Explicit aria-required values should trump native required attributes. 760 const AtomicString& requiredValue = getAttribute(aria_requiredAttr); 761 if (equalIgnoringCase(requiredValue, "true")) 762 return true; 763 if (equalIgnoringCase(requiredValue, "false")) 764 return false; 765 766 Node* n = this->node(); 767 if (n && (n->isElementNode() && toElement(n)->isFormControlElement())) 768 return toHTMLFormControlElement(n)->isRequired(); 769 770 return false; 771} 772 773bool AccessibilityNodeObject::supportsRequiredAttribute() const 774{ 775 switch (roleValue()) { 776 case ButtonRole: 777 return isFileUploadButton(); 778 case CellRole: 779 case CheckBoxRole: 780 case ComboBoxRole: 781 case GridRole: 782 case IncrementorRole: 783 case ListBoxRole: 784 case PopUpButtonRole: 785 case RadioButtonRole: 786 case RadioGroupRole: 787 case RowHeaderRole: 788 case SliderRole: 789 case SpinButtonRole: 790 case TableHeaderContainerRole: 791 case TextAreaRole: 792 case TextFieldRole: 793 case ToggleButtonRole: 794 return true; 795 default: 796 return false; 797 } 798} 799 800int AccessibilityNodeObject::headingLevel() const 801{ 802 // headings can be in block flow and non-block flow 803 Node* node = this->node(); 804 if (!node) 805 return false; 806 807 if (isHeading()) { 808 int ariaLevel = getAttribute(aria_levelAttr).toInt(); 809 if (ariaLevel > 0) 810 return ariaLevel; 811 } 812 813 if (node->hasTagName(h1Tag)) 814 return 1; 815 816 if (node->hasTagName(h2Tag)) 817 return 2; 818 819 if (node->hasTagName(h3Tag)) 820 return 3; 821 822 if (node->hasTagName(h4Tag)) 823 return 4; 824 825 if (node->hasTagName(h5Tag)) 826 return 5; 827 828 if (node->hasTagName(h6Tag)) 829 return 6; 830 831 return 0; 832} 833 834String AccessibilityNodeObject::valueDescription() const 835{ 836 if (!isRangeControl()) 837 return String(); 838 839 return getAttribute(aria_valuetextAttr).string(); 840} 841 842float AccessibilityNodeObject::valueForRange() const 843{ 844 if (node() && isHTMLInputElement(node())) { 845 HTMLInputElement* input = toHTMLInputElement(node()); 846 if (input->isRangeControl()) 847 return input->valueAsNumber(); 848 } 849 850 if (!isRangeControl()) 851 return 0.0f; 852 853 return getAttribute(aria_valuenowAttr).toFloat(); 854} 855 856float AccessibilityNodeObject::maxValueForRange() const 857{ 858 if (node() && isHTMLInputElement(node())) { 859 HTMLInputElement* input = toHTMLInputElement(node()); 860 if (input->isRangeControl()) 861 return input->maximum(); 862 } 863 864 if (!isRangeControl()) 865 return 0.0f; 866 867 return getAttribute(aria_valuemaxAttr).toFloat(); 868} 869 870float AccessibilityNodeObject::minValueForRange() const 871{ 872 if (node() && isHTMLInputElement(node())) { 873 HTMLInputElement* input = toHTMLInputElement(node()); 874 if (input->isRangeControl()) 875 return input->minimum(); 876 } 877 878 if (!isRangeControl()) 879 return 0.0f; 880 881 return getAttribute(aria_valueminAttr).toFloat(); 882} 883 884float AccessibilityNodeObject::stepValueForRange() const 885{ 886 return getAttribute(stepAttr).toFloat(); 887} 888 889bool AccessibilityNodeObject::isHeading() const 890{ 891 return roleValue() == HeadingRole; 892} 893 894bool AccessibilityNodeObject::isLink() const 895{ 896 return roleValue() == WebCoreLinkRole; 897} 898 899bool AccessibilityNodeObject::isControl() const 900{ 901 Node* node = this->node(); 902 if (!node) 903 return false; 904 905 return ((node->isElementNode() && toElement(node)->isFormControlElement()) 906 || AccessibilityObject::isARIAControl(ariaRoleAttribute())); 907} 908 909bool AccessibilityNodeObject::isFieldset() const 910{ 911 Node* node = this->node(); 912 if (!node) 913 return false; 914 915 return node->hasTagName(fieldsetTag); 916} 917 918bool AccessibilityNodeObject::isGroup() const 919{ 920 return roleValue() == GroupRole; 921} 922 923AccessibilityObject* AccessibilityNodeObject::selectedRadioButton() 924{ 925 if (!isRadioGroup()) 926 return nullptr; 927 928 // Find the child radio button that is selected (ie. the intValue == 1). 929 for (const auto& child : children()) { 930 if (child->roleValue() == RadioButtonRole && child->checkboxOrRadioValue() == ButtonStateOn) 931 return child.get(); 932 } 933 return nullptr; 934} 935 936AccessibilityObject* AccessibilityNodeObject::selectedTabItem() 937{ 938 if (!isTabList()) 939 return nullptr; 940 941 // Find the child tab item that is selected (ie. the intValue == 1). 942 AccessibilityObject::AccessibilityChildrenVector tabs; 943 tabChildren(tabs); 944 945 for (const auto& child : children()) { 946 if (child->isTabItem() && child->isChecked()) 947 return child.get(); 948 } 949 return nullptr; 950} 951 952AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const 953{ 954 if (isNativeCheckboxOrRadio()) 955 return isChecked() ? ButtonStateOn : ButtonStateOff; 956 957 return AccessibilityObject::checkboxOrRadioValue(); 958} 959 960Element* AccessibilityNodeObject::anchorElement() const 961{ 962 Node* node = this->node(); 963 if (!node) 964 return 0; 965 966 AXObjectCache* cache = axObjectCache(); 967 968 // search up the DOM tree for an anchor element 969 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement 970 for ( ; node; node = node->parentNode()) { 971 if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) 972 return toElement(node); 973 } 974 975 return 0; 976} 977 978static bool isNodeActionElement(Node* node) 979{ 980 if (isHTMLInputElement(node)) { 981 HTMLInputElement* input = toHTMLInputElement(node); 982 if (!input->isDisabledFormControl() && (input->isRadioButton() || input->isCheckbox() || input->isTextButton() || input->isFileUpload() || input->isImageButton())) 983 return true; 984 } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag)) 985 return true; 986 987 return false; 988} 989 990static Element* nativeActionElement(Node* start) 991{ 992 if (!start) 993 return 0; 994 995 // Do a deep-dive to see if any nodes should be used as the action element. 996 // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons). 997 // It solves the problem when authors put role="button" on a group and leave the actual button inside the group. 998 999 for (Node* child = start->firstChild(); child; child = child->nextSibling()) { 1000 if (isNodeActionElement(child)) 1001 return toElement(child); 1002 1003 if (Element* subChild = nativeActionElement(child)) 1004 return subChild; 1005 } 1006 return 0; 1007} 1008 1009Element* AccessibilityNodeObject::actionElement() const 1010{ 1011 Node* node = this->node(); 1012 if (!node) 1013 return 0; 1014 1015 if (isNodeActionElement(node)) 1016 return toElement(node); 1017 1018 if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) 1019 return toElement(node); 1020 1021 switch (roleValue()) { 1022 case ButtonRole: 1023 case PopUpButtonRole: 1024 case ToggleButtonRole: 1025 case TabRole: 1026 case MenuItemRole: 1027 case MenuItemCheckboxRole: 1028 case MenuItemRadioRole: 1029 case ListItemRole: 1030 // Check if the author is hiding the real control element inside the ARIA element. 1031 if (Element* nativeElement = nativeActionElement(node)) 1032 return nativeElement; 1033 return toElement(node); 1034 default: 1035 break; 1036 } 1037 1038 Element* elt = anchorElement(); 1039 if (!elt) 1040 elt = mouseButtonListener(); 1041 return elt; 1042} 1043 1044Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const 1045{ 1046 Node* node = this->node(); 1047 if (!node) 1048 return 0; 1049 1050 // check if our parent is a mouse button listener 1051 // FIXME: Do the continuation search like anchorElement does 1052 for (auto& element : elementLineage(node->isElementNode() ? toElement(node) : node->parentElement())) { 1053 // If we've reached the body and this is not a control element, do not expose press action for this element unless filter is IncludeBodyElement. 1054 // It can cause false positives, where every piece of text is labeled as accepting press actions. 1055 if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement) 1056 break; 1057 1058 if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent)) 1059 return &element; 1060 } 1061 1062 return 0; 1063} 1064 1065bool AccessibilityNodeObject::isDescendantOfBarrenParent() const 1066{ 1067 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { 1068 if (!object->canHaveChildren()) 1069 return true; 1070 } 1071 1072 return false; 1073} 1074 1075void AccessibilityNodeObject::alterSliderValue(bool increase) 1076{ 1077 if (roleValue() != SliderRole) 1078 return; 1079 1080 if (!getAttribute(stepAttr).isEmpty()) 1081 changeValueByStep(increase); 1082 else 1083 changeValueByPercent(increase ? 5 : -5); 1084} 1085 1086void AccessibilityNodeObject::increment() 1087{ 1088 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 1089 alterSliderValue(true); 1090} 1091 1092void AccessibilityNodeObject::decrement() 1093{ 1094 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); 1095 alterSliderValue(false); 1096} 1097 1098void AccessibilityNodeObject::changeValueByStep(bool increase) 1099{ 1100 float step = stepValueForRange(); 1101 float value = valueForRange(); 1102 1103 value += increase ? step : -step; 1104 1105 setValue(String::number(value)); 1106 1107 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged); 1108} 1109 1110void AccessibilityNodeObject::changeValueByPercent(float percentChange) 1111{ 1112 float range = maxValueForRange() - minValueForRange(); 1113 float step = range * (percentChange / 100); 1114 float value = valueForRange(); 1115 1116 // Make sure the specified percent will cause a change of one integer step or larger. 1117 if (fabs(step) < 1) 1118 step = fabs(percentChange) * (1 / percentChange); 1119 1120 value += step; 1121 setValue(String::number(value)); 1122 1123 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged); 1124} 1125 1126bool AccessibilityNodeObject::isGenericFocusableElement() const 1127{ 1128 if (!canSetFocusAttribute()) 1129 return false; 1130 1131 // If it's a control, it's not generic. 1132 if (isControl()) 1133 return false; 1134 1135 AccessibilityRole role = roleValue(); 1136 if (role == VideoRole || role == AudioRole) 1137 return false; 1138 1139 // If it has an aria role, it's not generic. 1140 if (m_ariaRole != UnknownRole) 1141 return false; 1142 1143 // If the content editable attribute is set on this element, that's the reason 1144 // it's focusable, and existing logic should handle this case already - so it's not a 1145 // generic focusable element. 1146 1147 if (hasContentEditableAttributeSet()) 1148 return false; 1149 1150 // The web area and body element are both focusable, but existing logic handles these 1151 // cases already, so we don't need to include them here. 1152 if (role == WebAreaRole) 1153 return false; 1154 if (node() && node()->hasTagName(bodyTag)) 1155 return false; 1156 1157 // An SVG root is focusable by default, but it's probably not interactive, so don't 1158 // include it. It can still be made accessible by giving it an ARIA role. 1159 if (role == SVGRootRole) 1160 return false; 1161 1162 return true; 1163} 1164 1165HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const 1166{ 1167 if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable()) 1168 return 0; 1169 1170 const AtomicString& id = element->getIdAttribute(); 1171 if (!id.isEmpty()) { 1172 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id)) 1173 return label; 1174 } 1175 1176 return ancestorsOfType<HTMLLabelElement>(*element).first(); 1177} 1178 1179String AccessibilityNodeObject::ariaAccessibilityDescription() const 1180{ 1181 String ariaLabeledBy = ariaLabeledByAttribute(); 1182 if (!ariaLabeledBy.isEmpty()) 1183 return ariaLabeledBy; 1184 1185 const AtomicString& ariaLabel = getAttribute(aria_labelAttr); 1186 if (!ariaLabel.isEmpty()) 1187 return ariaLabel; 1188 1189 return String(); 1190} 1191 1192static Element* siblingWithAriaRole(String role, Node* node) 1193{ 1194 ContainerNode* parent = node->parentNode(); 1195 if (!parent) 1196 return nullptr; 1197 1198 for (auto& sibling : childrenOfType<Element>(*parent)) { 1199 const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr); 1200 if (equalIgnoringCase(siblingAriaRole, role)) 1201 return &sibling; 1202 } 1203 1204 return nullptr; 1205} 1206 1207Element* AccessibilityNodeObject::menuElementForMenuButton() const 1208{ 1209 if (ariaRoleAttribute() != MenuButtonRole) 1210 return nullptr; 1211 1212 return siblingWithAriaRole("menu", node()); 1213} 1214 1215AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const 1216{ 1217 if (AXObjectCache* cache = axObjectCache()) 1218 return cache->getOrCreate(menuElementForMenuButton()); 1219 return nullptr; 1220} 1221 1222Element* AccessibilityNodeObject::menuItemElementForMenu() const 1223{ 1224 if (ariaRoleAttribute() != MenuRole) 1225 return 0; 1226 1227 return siblingWithAriaRole("menuitem", node()); 1228} 1229 1230AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const 1231{ 1232 AXObjectCache* cache = axObjectCache(); 1233 if (!cache) 1234 return nullptr; 1235 1236 Element* menuItem = menuItemElementForMenu(); 1237 1238 if (menuItem) { 1239 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem 1240 AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem); 1241 if (menuItemAX && menuItemAX->isMenuButton()) 1242 return menuItemAX; 1243 } 1244 return 0; 1245} 1246 1247bool AccessibilityNodeObject::usesAltTagForTextComputation() const 1248{ 1249 return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag)); 1250} 1251 1252void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const 1253{ 1254 Node* node = this->node(); 1255 if (!node) 1256 return; 1257 1258 bool isInputTag = isHTMLInputElement(node); 1259 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { 1260 HTMLLabelElement* label = labelForElement(toElement(node)); 1261 if (label) { 1262 AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label); 1263 String innerText = label->innerText(); 1264 // Only use the <label> text if there's no ARIA override. 1265 if (!innerText.isEmpty() && !ariaAccessibilityDescription()) 1266 textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject)); 1267 return; 1268 } 1269 } 1270 1271 AccessibilityObject* titleUIElement = this->titleUIElement(); 1272 if (titleUIElement) 1273 textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement)); 1274} 1275 1276void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const 1277{ 1278 if (isWebArea()) { 1279 String webAreaText = alternativeTextForWebArea(); 1280 if (!webAreaText.isEmpty()) 1281 textOrder.append(AccessibilityText(webAreaText, AlternativeText)); 1282 return; 1283 } 1284 1285 ariaLabeledByText(textOrder); 1286 1287 const AtomicString& ariaLabel = getAttribute(aria_labelAttr); 1288 if (!ariaLabel.isEmpty()) 1289 textOrder.append(AccessibilityText(ariaLabel, AlternativeText)); 1290 1291 if (usesAltTagForTextComputation()) { 1292 if (renderer() && renderer()->isRenderImage()) { 1293 String renderAltText = toRenderImage(renderer())->altText(); 1294 1295 // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText. 1296 if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) { 1297 textOrder.append(AccessibilityText(renderAltText, AlternativeText)); 1298 return; 1299 } 1300 } 1301 // Images should use alt as long as the attribute is present, even if empty. 1302 // Otherwise, it should fallback to other methods, like the title attribute. 1303 const AtomicString& alt = getAttribute(altAttr); 1304 if (!alt.isEmpty()) 1305 textOrder.append(AccessibilityText(alt, AlternativeText)); 1306 } 1307 1308 Node* node = this->node(); 1309 if (!node) 1310 return; 1311 1312 // The fieldset element derives its alternative text from the first associated legend element if one is available. 1313 if (isHTMLFieldSetElement(node)) { 1314 AccessibilityObject* object = axObjectCache()->getOrCreate(toHTMLFieldSetElement(node)->legend()); 1315 if (object && !object->isHidden()) 1316 textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText)); 1317 } 1318 1319 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text. 1320 if (node->isSVGElement()) 1321 textOrder.append(AccessibilityText(toSVGElement(node)->title(), AlternativeText)); 1322 1323#if ENABLE(MATHML) 1324 if (node->isMathMLElement()) 1325 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText)); 1326#endif 1327} 1328 1329void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const 1330{ 1331 Node* node = this->node(); 1332 if (!node) 1333 return; 1334 1335 bool isInputTag = isHTMLInputElement(node); 1336 if (isInputTag) { 1337 HTMLInputElement* input = toHTMLInputElement(node); 1338 if (input->isTextButton()) { 1339 textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText)); 1340 return; 1341 } 1342 } 1343 1344 // If this node isn't rendered, there's no inner text we can extract from a select element. 1345 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag)) 1346 return; 1347 1348 bool useTextUnderElement = false; 1349 1350 switch (roleValue()) { 1351 case PopUpButtonRole: 1352 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue(). 1353 if (node->hasTagName(selectTag)) 1354 break; 1355 FALLTHROUGH; 1356 case ButtonRole: 1357 case ToggleButtonRole: 1358 case CheckBoxRole: 1359 case ListBoxOptionRole: 1360 // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that. 1361#if !PLATFORM(COCOA) 1362 case ListItemRole: 1363#endif 1364 case MenuButtonRole: 1365 case MenuItemRole: 1366 case MenuItemCheckboxRole: 1367 case MenuItemRadioRole: 1368 case RadioButtonRole: 1369 case TabRole: 1370 case ProgressIndicatorRole: 1371 useTextUnderElement = true; 1372 break; 1373 default: 1374 break; 1375 } 1376 1377 // If it's focusable but it's not content editable or a known control type, then it will appear to 1378 // the user as a single atomic object, so we should use its text as the default title. 1379 if (isHeading() || isLink()) 1380 useTextUnderElement = true; 1381 1382 if (useTextUnderElement) { 1383 AccessibilityTextUnderElementMode mode; 1384 1385 // Headings often include links as direct children. Those links need to be included in text under element. 1386 if (isHeading()) 1387 mode.includeFocusableContent = true; 1388 1389 String text = textUnderElement(mode); 1390 if (!text.isEmpty()) 1391 textOrder.append(AccessibilityText(text, ChildrenText)); 1392 } 1393} 1394 1395void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const 1396{ 1397 const AtomicString& ariaHelp = getAttribute(aria_helpAttr); 1398 if (!ariaHelp.isEmpty()) 1399 textOrder.append(AccessibilityText(ariaHelp, HelpText)); 1400 1401 String describedBy = ariaDescribedByAttribute(); 1402 if (!describedBy.isEmpty()) 1403 textOrder.append(AccessibilityText(describedBy, SummaryText)); 1404 1405 // Summary attribute used as help text on tables. 1406 const AtomicString& summary = getAttribute(summaryAttr); 1407 if (!summary.isEmpty()) 1408 textOrder.append(AccessibilityText(summary, SummaryText)); 1409 1410 // The title attribute should be used as help text unless it is already being used as descriptive text. 1411 const AtomicString& title = getAttribute(titleAttr); 1412 if (!title.isEmpty()) 1413 textOrder.append(AccessibilityText(title, TitleTagText)); 1414} 1415 1416void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder) 1417{ 1418 titleElementText(textOrder); 1419 alternativeText(textOrder); 1420 visibleText(textOrder); 1421 helpText(textOrder); 1422 1423 String placeholder = placeholderValue(); 1424 if (!placeholder.isEmpty()) 1425 textOrder.append(AccessibilityText(placeholder, PlaceholderText)); 1426} 1427 1428void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const 1429{ 1430 String ariaLabeledBy = ariaLabeledByAttribute(); 1431 if (!ariaLabeledBy.isEmpty()) { 1432 Vector<Element*> elements; 1433 ariaLabeledByElements(elements); 1434 1435 Vector<RefPtr<AccessibilityObject>> axElements; 1436 for (const auto& element : elements) { 1437 RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(element); 1438 axElements.append(axElement); 1439 } 1440 1441 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTF::move(axElements))); 1442 } 1443} 1444 1445String AccessibilityNodeObject::alternativeTextForWebArea() const 1446{ 1447 // The WebArea description should follow this order: 1448 // aria-label on the <html> 1449 // title on the <html> 1450 // <title> inside the <head> (of it was set through JS) 1451 // name on the <html> 1452 // For iframes: 1453 // aria-label on the <iframe> 1454 // title on the <iframe> 1455 // name on the <iframe> 1456 1457 Document* document = this->document(); 1458 if (!document) 1459 return String(); 1460 1461 // Check if the HTML element has an aria-label for the webpage. 1462 if (Element* documentElement = document->documentElement()) { 1463 const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr); 1464 if (!ariaLabel.isEmpty()) 1465 return ariaLabel; 1466 } 1467 1468 Node* owner = document->ownerElement(); 1469 if (owner) { 1470 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { 1471 const AtomicString& title = toElement(owner)->getAttribute(titleAttr); 1472 if (!title.isEmpty()) 1473 return title; 1474 return toElement(owner)->getNameAttribute(); 1475 } 1476 if (owner->isHTMLElement()) 1477 return toHTMLElement(owner)->getNameAttribute(); 1478 } 1479 1480 String documentTitle = document->title(); 1481 if (!documentTitle.isEmpty()) 1482 return documentTitle; 1483 1484 owner = document->body(); 1485 if (owner && owner->isHTMLElement()) 1486 return toHTMLElement(owner)->getNameAttribute(); 1487 1488 return String(); 1489} 1490 1491String AccessibilityNodeObject::accessibilityDescription() const 1492{ 1493 // Static text should not have a description, it should only have a stringValue. 1494 if (roleValue() == StaticTextRole) 1495 return String(); 1496 1497 String ariaDescription = ariaAccessibilityDescription(); 1498 if (!ariaDescription.isEmpty()) 1499 return ariaDescription; 1500 1501 if (usesAltTagForTextComputation()) { 1502 // Images should use alt as long as the attribute is present, even if empty. 1503 // Otherwise, it should fallback to other methods, like the title attribute. 1504 const AtomicString& alt = getAttribute(altAttr); 1505 if (!alt.isNull()) 1506 return alt; 1507 } 1508 1509 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text. 1510 if (m_node && m_node->isSVGElement()) 1511 return toSVGElement(m_node)->title(); 1512 1513#if ENABLE(MATHML) 1514 if (m_node && m_node->isMathMLElement()) 1515 return getAttribute(MathMLNames::alttextAttr); 1516#endif 1517 1518 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text). 1519 // Both are used to generate what a screen reader speaks. 1520 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute. 1521 // 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). 1522 if (title().isEmpty()) 1523 return getAttribute(titleAttr); 1524 1525 return String(); 1526} 1527 1528String AccessibilityNodeObject::helpText() const 1529{ 1530 Node* node = this->node(); 1531 if (!node) 1532 return String(); 1533 1534 const AtomicString& ariaHelp = getAttribute(aria_helpAttr); 1535 if (!ariaHelp.isEmpty()) 1536 return ariaHelp; 1537 1538 String describedBy = ariaDescribedByAttribute(); 1539 if (!describedBy.isEmpty()) 1540 return describedBy; 1541 1542 String description = accessibilityDescription(); 1543 for (Node* curr = node; curr; curr = curr->parentNode()) { 1544 if (curr->isHTMLElement()) { 1545 const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr); 1546 if (!summary.isEmpty()) 1547 return summary; 1548 1549 // The title attribute should be used as help text unless it is already being used as descriptive text. 1550 const AtomicString& title = toElement(curr)->getAttribute(titleAttr); 1551 if (!title.isEmpty() && description != title) 1552 return title; 1553 } 1554 1555 // Only take help text from an ancestor element if its a group or an unknown role. If help was 1556 // added to those kinds of elements, it is likely it was meant for a child element. 1557 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); 1558 if (axObj) { 1559 AccessibilityRole role = axObj->roleValue(); 1560 if (role != GroupRole && role != UnknownRole) 1561 break; 1562 } 1563 } 1564 1565 return String(); 1566} 1567 1568unsigned AccessibilityNodeObject::hierarchicalLevel() const 1569{ 1570 Node* node = this->node(); 1571 if (!node || !node->isElementNode()) 1572 return 0; 1573 Element* element = toElement(node); 1574 String ariaLevel = element->getAttribute(aria_levelAttr); 1575 if (!ariaLevel.isEmpty()) 1576 return ariaLevel.toInt(); 1577 1578 // Only tree item will calculate its level through the DOM currently. 1579 if (roleValue() != TreeItemRole) 1580 return 0; 1581 1582 // Hierarchy leveling starts at 1, to match the aria-level spec. 1583 // We measure tree hierarchy by the number of groups that the item is within. 1584 unsigned level = 1; 1585 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { 1586 AccessibilityRole parentRole = parent->roleValue(); 1587 if (parentRole == GroupRole) 1588 level++; 1589 else if (parentRole == TreeRole) 1590 break; 1591 } 1592 1593 return level; 1594} 1595 1596// When building the textUnderElement for an object, determine whether or not 1597// we should include the inner text of this given descendant object or skip it. 1598static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode) 1599{ 1600 // Do not use any heuristic if we are explicitly asking to include all the children. 1601 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren) 1602 return true; 1603 1604 // Consider this hypothetical example: 1605 // <div tabindex=0> 1606 // <h2> 1607 // Table of contents 1608 // </h2> 1609 // <a href="#start">Jump to start of book</a> 1610 // <ul> 1611 // <li><a href="#1">Chapter 1</a></li> 1612 // <li><a href="#1">Chapter 2</a></li> 1613 // </ul> 1614 // </div> 1615 // 1616 // The goal is to return a reasonable title for the outer container div, because 1617 // it's focusable - but without making its title be the full inner text, which is 1618 // quite long. As a heuristic, skip links, controls, and elements that are usually 1619 // containers with lots of children. 1620 1621 if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true")) 1622 return false; 1623 1624 // If something doesn't expose any children, then we can always take the inner text content. 1625 // This is what we want when someone puts an <a> inside a <button> for example. 1626 if (obj->isDescendantOfBarrenParent()) 1627 return true; 1628 1629 // Skip focusable children, so we don't include the text of links and controls. 1630 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent) 1631 return false; 1632 1633 // Skip big container elements like lists, tables, etc. 1634 if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas()) 1635 return false; 1636 1637 return true; 1638} 1639 1640static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, String& childText) 1641{ 1642 if (!builder.length() || !childText.length()) 1643 return false; 1644 1645 // We don't need to add an additional space before or after a line break. 1646 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1])); 1647} 1648 1649String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const 1650{ 1651 Node* node = this->node(); 1652 if (node && node->isTextNode()) 1653 return toText(node)->wholeText(); 1654 1655 // The render tree should be stable before going ahead. Otherwise, further uses of the 1656 // TextIterator will force a layout update, potentially altering the accessibility tree 1657 // and leading to crashes in the loop that computes the result text from the children. 1658 ASSERT(!document()->renderView()->layoutState()); 1659 ASSERT(!document()->childNeedsStyleRecalc()); 1660 1661 StringBuilder builder; 1662 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) { 1663 if (!shouldUseAccessiblityObjectInnerText(child, mode)) 1664 continue; 1665 1666 if (child->isAccessibilityNodeObject()) { 1667 Vector<AccessibilityText> textOrder; 1668 toAccessibilityNodeObject(child)->alternativeText(textOrder); 1669 if (textOrder.size() > 0 && textOrder[0].text.length()) { 1670 if (shouldAddSpaceBeforeAppendingNextElement(builder, textOrder[0].text)) 1671 builder.append(' '); 1672 builder.append(textOrder[0].text); 1673 continue; 1674 } 1675 } 1676 1677 String childText = child->textUnderElement(mode); 1678 if (childText.length()) { 1679 if (shouldAddSpaceBeforeAppendingNextElement(builder, childText)) 1680 builder.append(' '); 1681 builder.append(childText); 1682 } 1683 } 1684 1685 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak); 1686} 1687 1688String AccessibilityNodeObject::title() const 1689{ 1690 Node* node = this->node(); 1691 if (!node) 1692 return String(); 1693 1694 bool isInputTag = isHTMLInputElement(node); 1695 if (isInputTag) { 1696 HTMLInputElement* input = toHTMLInputElement(node); 1697 if (input->isTextButton()) 1698 return input->valueWithDefault(); 1699 } 1700 1701 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { 1702 HTMLLabelElement* label = labelForElement(toElement(node)); 1703 // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override. 1704 if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length()) 1705 return label->innerText(); 1706 } 1707 1708 // If this node isn't rendered, there's no inner text we can extract from a select element. 1709 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag)) 1710 return String(); 1711 1712 switch (roleValue()) { 1713 case PopUpButtonRole: 1714 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue(). 1715 if (node->hasTagName(selectTag)) 1716 return String(); 1717 FALLTHROUGH; 1718 case ButtonRole: 1719 case ToggleButtonRole: 1720 case CheckBoxRole: 1721 case ListBoxOptionRole: 1722 case ListItemRole: 1723 case MenuButtonRole: 1724 case MenuItemRole: 1725 case MenuItemCheckboxRole: 1726 case MenuItemRadioRole: 1727 case RadioButtonRole: 1728 case TabRole: 1729 return textUnderElement(); 1730 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>. 1731 case SVGRootRole: 1732 return String(); 1733 default: 1734 break; 1735 } 1736 1737 if (isLink()) 1738 return textUnderElement(); 1739 if (isHeading()) 1740 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true)); 1741 1742 return String(); 1743} 1744 1745String AccessibilityNodeObject::text() const 1746{ 1747 // If this is a user defined static text, use the accessible name computation. 1748 if (ariaRoleAttribute() == StaticTextRole) { 1749 Vector<AccessibilityText> textOrder; 1750 alternativeText(textOrder); 1751 if (textOrder.size() > 0 && textOrder[0].text.length()) 1752 return textOrder[0].text; 1753 } 1754 1755 if (!isTextControl()) 1756 return String(); 1757 1758 Node* node = this->node(); 1759 if (!node) 1760 return String(); 1761 1762 if (isNativeTextControl() && (isHTMLTextAreaElement(node) || isHTMLInputElement(node))) 1763 return toHTMLTextFormControlElement(node)->value(); 1764 1765 if (!node->isElementNode()) 1766 return String(); 1767 1768 return toElement(node)->innerText(); 1769} 1770 1771String AccessibilityNodeObject::stringValue() const 1772{ 1773 Node* node = this->node(); 1774 if (!node) 1775 return String(); 1776 1777 if (ariaRoleAttribute() == StaticTextRole) { 1778 String staticText = text(); 1779 if (!staticText.length()) 1780 staticText = textUnderElement(); 1781 return staticText; 1782 } 1783 1784 if (node->isTextNode()) 1785 return textUnderElement(); 1786 1787 if (node->hasTagName(selectTag)) { 1788 HTMLSelectElement* selectElement = toHTMLSelectElement(node); 1789 int selectedIndex = selectElement->selectedIndex(); 1790 const Vector<HTMLElement*>& listItems = selectElement->listItems(); 1791 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { 1792 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); 1793 if (!overriddenDescription.isNull()) 1794 return overriddenDescription; 1795 } 1796 if (!selectElement->multiple()) 1797 return selectElement->value(); 1798 return String(); 1799 } 1800 1801 if (isTextControl()) 1802 return text(); 1803 1804 // FIXME: We might need to implement a value here for more types 1805 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; 1806 // this would require subclassing or making accessibilityAttributeNames do something other than return a 1807 // single static array. 1808 return String(); 1809} 1810 1811void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const 1812{ 1813 r = 0; 1814 g = 0; 1815 b = 0; 1816 1817 if (!isColorWell()) 1818 return; 1819 1820 if (!node() || !isHTMLInputElement(node())) 1821 return; 1822 1823 HTMLInputElement* input = toHTMLInputElement(node()); 1824 const AtomicString& type = input->getAttribute(typeAttr); 1825 if (!equalIgnoringCase(type, "color")) 1826 return; 1827 1828 // HTMLInputElement::value always returns a string parseable by Color(). 1829 Color color(input->value()); 1830 r = color.red(); 1831 g = color.green(); 1832 b = color.blue(); 1833} 1834 1835// This function implements the ARIA accessible name as described by the Mozilla 1836// ARIA Implementer's Guide. 1837static String accessibleNameForNode(Node* node) 1838{ 1839 ASSERT(node); 1840 if (!node || !node->isElementNode()) 1841 return String(); 1842 1843 Element* element = toElement(node); 1844 const AtomicString& ariaLabel = element->fastGetAttribute(aria_labelAttr); 1845 if (!ariaLabel.isEmpty()) 1846 return ariaLabel; 1847 1848 const AtomicString& alt = element->fastGetAttribute(altAttr); 1849 if (!alt.isEmpty()) 1850 return alt; 1851 1852 if (isHTMLInputElement(node)) 1853 return toHTMLInputElement(node)->value(); 1854 1855 // If the node can be turned into an AX object, we can use standard name computation rules. 1856 // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath. 1857 AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node); 1858 String text; 1859 if (axObject) 1860 text = axObject->textUnderElement(); 1861 else 1862 text = element->innerText(); 1863 1864 if (!text.isEmpty()) 1865 return text; 1866 1867 const AtomicString& title = element->fastGetAttribute(titleAttr); 1868 if (!title.isEmpty()) 1869 return title; 1870 1871 return String(); 1872} 1873 1874String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const 1875{ 1876 StringBuilder builder; 1877 unsigned size = elements.size(); 1878 for (unsigned i = 0; i < size; ++i) { 1879 if (i) 1880 builder.append(' '); 1881 1882 builder.append(accessibleNameForNode(elements[i])); 1883 } 1884 return builder.toString(); 1885} 1886 1887String AccessibilityNodeObject::ariaDescribedByAttribute() const 1888{ 1889 Vector<Element*> elements; 1890 elementsFromAttribute(elements, aria_describedbyAttr); 1891 1892 return accessibilityDescriptionForElements(elements); 1893} 1894 1895void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const 1896{ 1897 elementsFromAttribute(elements, aria_labelledbyAttr); 1898 if (!elements.size()) 1899 elementsFromAttribute(elements, aria_labeledbyAttr); 1900} 1901 1902 1903String AccessibilityNodeObject::ariaLabeledByAttribute() const 1904{ 1905 Vector<Element*> elements; 1906 ariaLabeledByElements(elements); 1907 1908 return accessibilityDescriptionForElements(elements); 1909} 1910 1911bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const 1912{ 1913 if (AccessibilityObject::hasAttributesRequiredForInclusion()) 1914 return true; 1915 1916 if (!ariaAccessibilityDescription().isEmpty()) 1917 return true; 1918 1919 return false; 1920} 1921 1922bool AccessibilityNodeObject::canSetFocusAttribute() const 1923{ 1924 Node* node = this->node(); 1925 if (!node) 1926 return false; 1927 1928 if (isWebArea()) 1929 return true; 1930 1931 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would 1932 // do anything. For example, setFocusedElement() will do nothing if the current focused 1933 // node will not relinquish the focus. 1934 if (!node) 1935 return false; 1936 1937 if (!node->isElementNode()) 1938 return false; 1939 1940 Element* element = toElement(node); 1941 1942 if (element->isDisabledFormControl()) 1943 return false; 1944 1945 return element->supportsFocus(); 1946} 1947 1948AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const 1949{ 1950 const AtomicString& ariaRole = getAttribute(roleAttr); 1951 if (ariaRole.isNull() || ariaRole.isEmpty()) 1952 return UnknownRole; 1953 1954 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole); 1955 1956 // ARIA states if an item can get focus, it should not be presentational. 1957 if (role == PresentationalRole && canSetFocusAttribute()) 1958 return UnknownRole; 1959 1960 if (role == ButtonRole) 1961 role = buttonRoleType(); 1962 1963 if (role == TextAreaRole && !ariaIsMultiline()) 1964 role = TextFieldRole; 1965 1966 role = remapAriaRoleDueToParent(role); 1967 1968 // Presentational roles are invalidated by the presence of ARIA attributes. 1969 if (role == PresentationalRole && supportsARIAAttributes()) 1970 role = UnknownRole; 1971 1972 if (role) 1973 return role; 1974 1975 return UnknownRole; 1976} 1977 1978AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const 1979{ 1980 return m_ariaRole; 1981} 1982 1983AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const 1984{ 1985 // Some objects change their role based on their parent. 1986 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop. 1987 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored(). 1988 // https://bugs.webkit.org/show_bug.cgi?id=65174 1989 1990 if (role != ListBoxOptionRole && role != MenuItemRole) 1991 return role; 1992 1993 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) { 1994 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute(); 1995 1996 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore. 1997 if (role == ListBoxOptionRole && parentAriaRole == MenuRole) 1998 return MenuItemRole; 1999 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent. 2000 if (role == MenuItemRole && parentAriaRole == GroupRole) 2001 return MenuButtonRole; 2002 2003 // If the parent had a different role, then we don't need to continue searching up the chain. 2004 if (parentAriaRole) 2005 break; 2006 } 2007 2008 return role; 2009} 2010 2011bool AccessibilityNodeObject::canSetSelectedAttribute() const 2012{ 2013 // Elements that can be selected 2014 switch (roleValue()) { 2015 case CellRole: 2016 case RadioButtonRole: 2017 case RowHeaderRole: 2018 case RowRole: 2019 case TabListRole: 2020 case TabRole: 2021 case TreeGridRole: 2022 case TreeItemRole: 2023 case TreeRole: 2024 return isEnabled(); 2025 default: 2026 return false; 2027 } 2028} 2029 2030} // namespace WebCore 2031