1/* 2* Copyright (C) 2008 Apple 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 "AccessibilityRenderObject.h" 31 32#include "AXObjectCache.h" 33#include "AccessibilityImageMapLink.h" 34#include "AccessibilityListBox.h" 35#include "EventNames.h" 36#include "FloatRect.h" 37#include "Frame.h" 38#include "FrameLoader.h" 39#include "HTMLAreaElement.h" 40#include "HTMLFormElement.h" 41#include "HTMLFrameElementBase.h" 42#include "HTMLImageElement.h" 43#include "HTMLInputElement.h" 44#include "HTMLLabelElement.h" 45#include "HTMLMapElement.h" 46#include "HTMLOptGroupElement.h" 47#include "HTMLOptionElement.h" 48#include "HTMLOptionsCollection.h" 49#include "HTMLSelectElement.h" 50#include "HTMLTextAreaElement.h" 51#include "HitTestRequest.h" 52#include "HitTestResult.h" 53#include "LocalizedStrings.h" 54#include "MathMLNames.h" 55#include "NodeList.h" 56#include "ProgressTracker.h" 57#include "RenderButton.h" 58#include "RenderFieldset.h" 59#include "RenderFileUploadControl.h" 60#include "RenderHTMLCanvas.h" 61#include "RenderImage.h" 62#include "RenderInline.h" 63#include "RenderLayer.h" 64#include "RenderListBox.h" 65#include "RenderListMarker.h" 66#include "RenderMenuList.h" 67#include "RenderText.h" 68#include "RenderTextControl.h" 69#include "RenderTextFragment.h" 70#include "RenderTheme.h" 71#include "RenderView.h" 72#include "RenderWidget.h" 73#include "SelectElement.h" 74#include "SelectionController.h" 75#include "Text.h" 76#include "TextIterator.h" 77#include "htmlediting.h" 78#include "visible_units.h" 79#include <wtf/StdLibExtras.h> 80#include <wtf/text/StringBuilder.h> 81#include <wtf/unicode/CharacterNames.h> 82 83using namespace std; 84 85namespace WebCore { 86 87using namespace HTMLNames; 88 89AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer) 90 : AccessibilityObject() 91 , m_renderer(renderer) 92 , m_ariaRole(UnknownRole) 93 , m_childrenDirty(false) 94 , m_roleForMSAA(UnknownRole) 95{ 96 m_role = determineAccessibilityRole(); 97 98#ifndef NDEBUG 99 m_renderer->setHasAXObject(true); 100#endif 101} 102 103AccessibilityRenderObject::~AccessibilityRenderObject() 104{ 105 ASSERT(isDetached()); 106} 107 108PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) 109{ 110 return adoptRef(new AccessibilityRenderObject(renderer)); 111} 112 113void AccessibilityRenderObject::detach() 114{ 115 clearChildren(); 116 AccessibilityObject::detach(); 117 118#ifndef NDEBUG 119 if (m_renderer) 120 m_renderer->setHasAXObject(false); 121#endif 122 m_renderer = 0; 123} 124 125RenderBoxModelObject* AccessibilityRenderObject::renderBoxModelObject() const 126{ 127 if (!m_renderer || !m_renderer->isBoxModelObject()) 128 return 0; 129 return toRenderBoxModelObject(m_renderer); 130} 131 132static inline bool isInlineWithContinuation(RenderObject* object) 133{ 134 if (!object->isBoxModelObject()) 135 return false; 136 137 RenderBoxModelObject* renderer = toRenderBoxModelObject(object); 138 if (!renderer->isRenderInline()) 139 return false; 140 141 return toRenderInline(renderer)->continuation(); 142} 143 144static inline RenderObject* firstChildInContinuation(RenderObject* renderer) 145{ 146 RenderObject* r = toRenderInline(renderer)->continuation(); 147 148 while (r) { 149 if (r->isRenderBlock()) 150 return r; 151 if (RenderObject* child = r->firstChild()) 152 return child; 153 r = toRenderInline(r)->continuation(); 154 } 155 156 return 0; 157} 158 159static inline RenderObject* firstChildConsideringContinuation(RenderObject* renderer) 160{ 161 RenderObject* firstChild = renderer->firstChild(); 162 163 if (!firstChild && isInlineWithContinuation(renderer)) 164 firstChild = firstChildInContinuation(renderer); 165 166 return firstChild; 167} 168 169 170static inline RenderObject* lastChildConsideringContinuation(RenderObject* renderer) 171{ 172 RenderObject* lastChild = renderer->lastChild(); 173 RenderObject* prev; 174 RenderObject* cur = renderer; 175 176 if (!cur->isRenderInline() && !cur->isRenderBlock()) 177 return renderer; 178 179 while (cur) { 180 prev = cur; 181 182 if (RenderObject* lc = cur->lastChild()) 183 lastChild = lc; 184 185 if (cur->isRenderInline()) { 186 cur = toRenderInline(cur)->inlineElementContinuation(); 187 ASSERT(cur || !toRenderInline(prev)->continuation()); 188 } else 189 cur = toRenderBlock(cur)->inlineElementContinuation(); 190 } 191 192 return lastChild; 193} 194 195AccessibilityObject* AccessibilityRenderObject::firstChild() const 196{ 197 if (!m_renderer) 198 return 0; 199 200 RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); 201 202 if (!firstChild) 203 return 0; 204 205 return axObjectCache()->getOrCreate(firstChild); 206} 207 208AccessibilityObject* AccessibilityRenderObject::lastChild() const 209{ 210 if (!m_renderer) 211 return 0; 212 213 RenderObject* lastChild = lastChildConsideringContinuation(m_renderer); 214 215 if (!lastChild) 216 return 0; 217 218 return axObjectCache()->getOrCreate(lastChild); 219} 220 221static inline RenderInline* startOfContinuations(RenderObject* r) 222{ 223 if (r->isInlineElementContinuation()) 224 return toRenderInline(r->node()->renderer()); 225 226 // Blocks with a previous continuation always have a next continuation 227 if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) 228 return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->node()->renderer()); 229 230 return 0; 231} 232 233static inline RenderObject* endOfContinuations(RenderObject* renderer) 234{ 235 RenderObject* prev = renderer; 236 RenderObject* cur = renderer; 237 238 if (!cur->isRenderInline() && !cur->isRenderBlock()) 239 return renderer; 240 241 while (cur) { 242 prev = cur; 243 if (cur->isRenderInline()) { 244 cur = toRenderInline(cur)->inlineElementContinuation(); 245 ASSERT(cur || !toRenderInline(prev)->continuation()); 246 } else 247 cur = toRenderBlock(cur)->inlineElementContinuation(); 248 } 249 250 return prev; 251} 252 253 254static inline RenderObject* childBeforeConsideringContinuations(RenderInline* r, RenderObject* child) 255{ 256 RenderBoxModelObject* curContainer = r; 257 RenderObject* cur = 0; 258 RenderObject* prev = 0; 259 260 while (curContainer) { 261 if (curContainer->isRenderInline()) { 262 cur = curContainer->firstChild(); 263 while (cur) { 264 if (cur == child) 265 return prev; 266 prev = cur; 267 cur = cur->nextSibling(); 268 } 269 270 curContainer = toRenderInline(curContainer)->continuation(); 271 } else if (curContainer->isRenderBlock()) { 272 if (curContainer == child) 273 return prev; 274 275 prev = curContainer; 276 curContainer = toRenderBlock(curContainer)->inlineElementContinuation(); 277 } 278 } 279 280 ASSERT_NOT_REACHED(); 281 282 return 0; 283} 284 285static inline bool firstChildIsInlineContinuation(RenderObject* renderer) 286{ 287 return renderer->firstChild() && renderer->firstChild()->isInlineElementContinuation(); 288} 289 290AccessibilityObject* AccessibilityRenderObject::previousSibling() const 291{ 292 if (!m_renderer) 293 return 0; 294 295 RenderObject* previousSibling = 0; 296 297 // Case 1: The node is a block and is an inline's continuation. In that case, the inline's 298 // last child is our previous sibling (or further back in the continuation chain) 299 RenderInline* startOfConts; 300 if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) 301 previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer); 302 303 // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before 304 // the parent of the start, since everything in between will be linked up via the continuation. 305 else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(m_renderer)) 306 previousSibling = startOfContinuations(m_renderer->firstChild())->parent()->previousSibling(); 307 308 // Case 3: The node has an actual previous sibling 309 else if (RenderObject* ps = m_renderer->previousSibling()) 310 previousSibling = ps; 311 312 // Case 4: This node has no previous siblings, but its parent is an inline, 313 // and is another node's inline continutation. Follow the continuation chain. 314 else if (m_renderer->parent()->isRenderInline() && (startOfConts = startOfContinuations(m_renderer->parent()))) 315 previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer->parent()->firstChild()); 316 317 if (!previousSibling) 318 return 0; 319 320 return axObjectCache()->getOrCreate(previousSibling); 321} 322 323static inline bool lastChildHasContinuation(RenderObject* renderer) 324{ 325 return renderer->lastChild() && isInlineWithContinuation(renderer->lastChild()); 326} 327 328AccessibilityObject* AccessibilityRenderObject::nextSibling() const 329{ 330 if (!m_renderer) 331 return 0; 332 333 RenderObject* nextSibling = 0; 334 335 // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's 336 // first child. 337 RenderInline* inlineContinuation; 338 if (m_renderer->isRenderBlock() && (inlineContinuation = toRenderBlock(m_renderer)->inlineElementContinuation())) 339 nextSibling = firstChildConsideringContinuation(inlineContinuation); 340 341 // Case 2: Anonymous block parent of the start of a continuation - skip all the way to 342 // after the parent of the end, since everything in between will be linked up via the continuation. 343 else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_renderer)) 344 nextSibling = endOfContinuations(m_renderer->lastChild())->parent()->nextSibling(); 345 346 // Case 3: node has an actual next sibling 347 else if (RenderObject* ns = m_renderer->nextSibling()) 348 nextSibling = ns; 349 350 // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end 351 // of the continuation chain. 352 else if (isInlineWithContinuation(m_renderer)) 353 nextSibling = endOfContinuations(m_renderer)->nextSibling(); 354 355 // Case 5: node has no next sibling, and its parent is an inline with a continuation. 356 else if (isInlineWithContinuation(m_renderer->parent())) { 357 RenderObject* continuation = toRenderInline(m_renderer->parent())->continuation(); 358 359 // Case 5a: continuation is a block - in this case the block itself is the next sibling. 360 if (continuation->isRenderBlock()) 361 nextSibling = continuation; 362 // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling 363 else 364 nextSibling = firstChildConsideringContinuation(continuation); 365 } 366 367 if (!nextSibling) 368 return 0; 369 370 return axObjectCache()->getOrCreate(nextSibling); 371} 372 373static RenderBoxModelObject* nextContinuation(RenderObject* renderer) 374{ 375 ASSERT(renderer); 376 if (renderer->isRenderInline() && !renderer->isReplaced()) 377 return toRenderInline(renderer)->continuation(); 378 if (renderer->isRenderBlock()) 379 return toRenderBlock(renderer)->inlineElementContinuation(); 380 return 0; 381} 382 383RenderObject* AccessibilityRenderObject::renderParentObject() const 384{ 385 if (!m_renderer) 386 return 0; 387 388 RenderObject* parent = m_renderer->parent(); 389 390 // Case 1: node is a block and is an inline's continuation. Parent 391 // is the start of the continuation chain. 392 RenderObject* startOfConts = 0; 393 RenderObject* firstChild = 0; 394 if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) 395 parent = startOfConts; 396 397 // Case 2: node's parent is an inline which is some node's continuation; parent is 398 // the earliest node in the continuation chain. 399 else if (parent && parent->isRenderInline() && (startOfConts = startOfContinuations(parent))) 400 parent = startOfConts; 401 402 // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation. 403 else if (parent && (firstChild = parent->firstChild()) && firstChild->node()) { 404 // Get the node's renderer and follow that continuation chain until the first child is found 405 RenderObject* nodeRenderFirstChild = firstChild->node()->renderer(); 406 if (nodeRenderFirstChild != firstChild) { 407 for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(contsTest)) { 408 if (contsTest == firstChild) { 409 parent = nodeRenderFirstChild->parent(); 410 break; 411 } 412 } 413 } 414 } 415 416 return parent; 417} 418 419AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const 420{ 421 return axObjectCache()->get(renderParentObject()); 422} 423 424AccessibilityObject* AccessibilityRenderObject::parentObject() const 425{ 426 if (!m_renderer) 427 return 0; 428 429 if (ariaRoleAttribute() == MenuBarRole) 430 return axObjectCache()->getOrCreate(m_renderer->parent()); 431 432 // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child 433 if (ariaRoleAttribute() == MenuRole) { 434 AccessibilityObject* parent = menuButtonForMenu(); 435 if (parent) 436 return parent; 437 } 438 439 RenderObject* parentObj = renderParentObject(); 440 if (parentObj) 441 return axObjectCache()->getOrCreate(parentObj); 442 443 // WebArea's parent should be the scroll view containing it. 444 if (isWebArea()) 445 return axObjectCache()->getOrCreate(m_renderer->frame()->view()); 446 447 return 0; 448} 449 450bool AccessibilityRenderObject::isWebArea() const 451{ 452 return roleValue() == WebAreaRole; 453} 454 455bool AccessibilityRenderObject::isImageButton() const 456{ 457 return isNativeImage() && roleValue() == ButtonRole; 458} 459 460bool AccessibilityRenderObject::isAnchor() const 461{ 462 return !isNativeImage() && isLink(); 463} 464 465bool AccessibilityRenderObject::isNativeTextControl() const 466{ 467 return m_renderer->isTextControl(); 468} 469 470bool AccessibilityRenderObject::isNativeImage() const 471{ 472 return m_renderer->isBoxModelObject() && toRenderBoxModelObject(m_renderer)->isImage(); 473} 474 475bool AccessibilityRenderObject::isImage() const 476{ 477 return roleValue() == ImageRole; 478} 479 480bool AccessibilityRenderObject::isAttachment() const 481{ 482 RenderBoxModelObject* renderer = renderBoxModelObject(); 483 if (!renderer) 484 return false; 485 // Widgets are the replaced elements that we represent to AX as attachments 486 bool isWidget = renderer->isWidget(); 487 ASSERT(!isWidget || (renderer->isReplaced() && !isImage())); 488 return isWidget && ariaRoleAttribute() == UnknownRole; 489} 490 491bool AccessibilityRenderObject::isPasswordField() const 492{ 493 ASSERT(m_renderer); 494 if (!m_renderer->node() || !m_renderer->node()->isHTMLElement()) 495 return false; 496 if (ariaRoleAttribute() != UnknownRole) 497 return false; 498 499 InputElement* inputElement = m_renderer->node()->toInputElement(); 500 if (!inputElement) 501 return false; 502 503 return inputElement->isPasswordField(); 504} 505 506bool AccessibilityRenderObject::isFileUploadButton() const 507{ 508 if (m_renderer && m_renderer->node() && m_renderer->node()->hasTagName(inputTag)) { 509 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->node()); 510 return input->isFileUpload(); 511 } 512 513 return false; 514} 515 516bool AccessibilityRenderObject::isInputImage() const 517{ 518 Node* elementNode = node(); 519 if (roleValue() == ButtonRole && elementNode && elementNode->hasTagName(inputTag)) { 520 HTMLInputElement* input = static_cast<HTMLInputElement*>(elementNode); 521 return input->isImageButton(); 522 } 523 524 return false; 525} 526 527bool AccessibilityRenderObject::isProgressIndicator() const 528{ 529 return roleValue() == ProgressIndicatorRole; 530} 531 532bool AccessibilityRenderObject::isSlider() const 533{ 534 return roleValue() == SliderRole; 535} 536 537bool AccessibilityRenderObject::isMenuRelated() const 538{ 539 AccessibilityRole role = roleValue(); 540 return role == MenuRole 541 || role == MenuBarRole 542 || role == MenuButtonRole 543 || role == MenuItemRole; 544} 545 546bool AccessibilityRenderObject::isMenu() const 547{ 548 return roleValue() == MenuRole; 549} 550 551bool AccessibilityRenderObject::isMenuBar() const 552{ 553 return roleValue() == MenuBarRole; 554} 555 556bool AccessibilityRenderObject::isMenuButton() const 557{ 558 return roleValue() == MenuButtonRole; 559} 560 561bool AccessibilityRenderObject::isMenuItem() const 562{ 563 return roleValue() == MenuItemRole; 564} 565 566bool AccessibilityRenderObject::isPressed() const 567{ 568 ASSERT(m_renderer); 569 if (roleValue() != ButtonRole) 570 return false; 571 572 Node* node = m_renderer->node(); 573 if (!node) 574 return false; 575 576 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active() 577 if (ariaRoleAttribute() == ButtonRole) { 578 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true")) 579 return true; 580 return false; 581 } 582 583 return node->active(); 584} 585 586bool AccessibilityRenderObject::isIndeterminate() const 587{ 588 ASSERT(m_renderer); 589 if (!m_renderer->node()) 590 return false; 591 592 InputElement* inputElement = m_renderer->node()->toInputElement(); 593 if (!inputElement) 594 return false; 595 596 return inputElement->isIndeterminate(); 597} 598 599bool AccessibilityRenderObject::isNativeCheckboxOrRadio() const 600{ 601 Node* elementNode = node(); 602 if (elementNode) { 603 InputElement* input = elementNode->toInputElement(); 604 if (input) 605 return input->isCheckbox() || input->isRadioButton(); 606 } 607 608 return false; 609} 610 611bool AccessibilityRenderObject::isChecked() const 612{ 613 ASSERT(m_renderer); 614 if (!m_renderer->node()) 615 return false; 616 617 // First test for native checkedness semantics 618 InputElement* inputElement = m_renderer->node()->toInputElement(); 619 if (inputElement) 620 return inputElement->isChecked(); 621 622 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute 623 AccessibilityRole ariaRole = ariaRoleAttribute(); 624 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) { 625 if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true")) 626 return true; 627 return false; 628 } 629 630 // Otherwise it's not checked 631 return false; 632} 633 634bool AccessibilityRenderObject::isHovered() const 635{ 636 ASSERT(m_renderer); 637 return m_renderer->node() && m_renderer->node()->hovered(); 638} 639 640bool AccessibilityRenderObject::isMultiSelectable() const 641{ 642 ASSERT(m_renderer); 643 644 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr); 645 if (equalIgnoringCase(ariaMultiSelectable, "true")) 646 return true; 647 if (equalIgnoringCase(ariaMultiSelectable, "false")) 648 return false; 649 650 if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isListBox()) 651 return false; 652 return m_renderer->node() && static_cast<HTMLSelectElement*>(m_renderer->node())->multiple(); 653} 654 655bool AccessibilityRenderObject::isReadOnly() const 656{ 657 ASSERT(m_renderer); 658 659 if (isWebArea()) { 660 Document* document = m_renderer->document(); 661 if (!document) 662 return true; 663 664 HTMLElement* body = document->body(); 665 if (body && body->isContentEditable()) 666 return false; 667 668 return !document->rendererIsEditable(); 669 } 670 671 if (m_renderer->isBoxModelObject()) { 672 RenderBoxModelObject* box = toRenderBoxModelObject(m_renderer); 673 if (box->isTextField()) 674 return static_cast<HTMLInputElement*>(box->node())->readOnly(); 675 if (box->isTextArea()) 676 return static_cast<HTMLTextAreaElement*>(box->node())->readOnly(); 677 } 678 679 return !m_renderer->node() || !m_renderer->node()->rendererIsEditable(); 680} 681 682bool AccessibilityRenderObject::isOffScreen() const 683{ 684 ASSERT(m_renderer); 685 IntRect contentRect = m_renderer->absoluteClippedOverflowRect(); 686 FrameView* view = m_renderer->frame()->view(); 687 FloatRect viewRect = view->visibleContentRect(); 688 viewRect.intersect(contentRect); 689 return viewRect.isEmpty(); 690} 691 692int AccessibilityRenderObject::headingLevel() const 693{ 694 // headings can be in block flow and non-block flow 695 Node* element = node(); 696 if (!element) 697 return 0; 698 699 if (ariaRoleAttribute() == HeadingRole) 700 return getAttribute(aria_levelAttr).toInt(); 701 702 if (element->hasTagName(h1Tag)) 703 return 1; 704 705 if (element->hasTagName(h2Tag)) 706 return 2; 707 708 if (element->hasTagName(h3Tag)) 709 return 3; 710 711 if (element->hasTagName(h4Tag)) 712 return 4; 713 714 if (element->hasTagName(h5Tag)) 715 return 5; 716 717 if (element->hasTagName(h6Tag)) 718 return 6; 719 720 return 0; 721} 722 723bool AccessibilityRenderObject::isHeading() const 724{ 725 return roleValue() == HeadingRole; 726} 727 728bool AccessibilityRenderObject::isLink() const 729{ 730 return roleValue() == WebCoreLinkRole; 731} 732 733bool AccessibilityRenderObject::isControl() const 734{ 735 if (!m_renderer) 736 return false; 737 738 Node* node = m_renderer->node(); 739 return node && ((node->isElementNode() && static_cast<Element*>(node)->isFormControlElement()) 740 || AccessibilityObject::isARIAControl(ariaRoleAttribute())); 741} 742 743bool AccessibilityRenderObject::isFieldset() const 744{ 745 RenderBoxModelObject* renderer = renderBoxModelObject(); 746 if (!renderer) 747 return false; 748 return renderer->isFieldset(); 749} 750 751bool AccessibilityRenderObject::isGroup() const 752{ 753 return roleValue() == GroupRole; 754} 755 756AccessibilityObject* AccessibilityRenderObject::selectedRadioButton() 757{ 758 if (!isRadioGroup()) 759 return 0; 760 761 // Find the child radio button that is selected (ie. the intValue == 1). 762 int count = m_children.size(); 763 for (int i = 0; i < count; ++i) { 764 AccessibilityObject* object = m_children[i].get(); 765 if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn) 766 return object; 767 } 768 return 0; 769} 770 771AccessibilityObject* AccessibilityRenderObject::selectedTabItem() 772{ 773 if (!isTabList()) 774 return 0; 775 776 // Find the child tab item that is selected (ie. the intValue == 1). 777 AccessibilityObject::AccessibilityChildrenVector tabs; 778 tabChildren(tabs); 779 780 int count = tabs.size(); 781 for (int i = 0; i < count; ++i) { 782 AccessibilityObject* object = m_children[i].get(); 783 if (object->isTabItem() && object->isChecked()) 784 return object; 785 } 786 return 0; 787} 788 789Element* AccessibilityRenderObject::anchorElement() const 790{ 791 if (!m_renderer) 792 return 0; 793 794 AXObjectCache* cache = axObjectCache(); 795 RenderObject* currRenderer; 796 797 // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. 798 for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) { 799 if (currRenderer->isAnonymousBlock()) { 800 RenderObject* continuation = toRenderBlock(currRenderer)->continuation(); 801 if (continuation) 802 return cache->getOrCreate(continuation)->anchorElement(); 803 } 804 } 805 806 // bail if none found 807 if (!currRenderer) 808 return 0; 809 810 // search up the DOM tree for an anchor element 811 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement 812 Node* node = currRenderer->node(); 813 for ( ; node; node = node->parentNode()) { 814 if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) 815 return static_cast<Element*>(node); 816 } 817 818 return 0; 819} 820 821Element* AccessibilityRenderObject::actionElement() const 822{ 823 if (!m_renderer) 824 return 0; 825 826 Node* node = m_renderer->node(); 827 if (node) { 828 if (node->hasTagName(inputTag)) { 829 HTMLInputElement* input = static_cast<HTMLInputElement*>(node); 830 if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton())) 831 return input; 832 } else if (node->hasTagName(buttonTag)) 833 return static_cast<Element*>(node); 834 } 835 836 if (isFileUploadButton()) 837 return static_cast<Element*>(m_renderer->node()); 838 839 if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) 840 return static_cast<Element*>(m_renderer->node()); 841 842 if (isImageButton()) 843 return static_cast<Element*>(m_renderer->node()); 844 845 if (m_renderer->isBoxModelObject() && toRenderBoxModelObject(m_renderer)->isMenuList()) 846 return static_cast<Element*>(m_renderer->node()); 847 848 AccessibilityRole role = roleValue(); 849 if (role == ButtonRole || role == PopUpButtonRole) 850 return static_cast<Element*>(m_renderer->node()); 851 852 Element* elt = anchorElement(); 853 if (!elt) 854 elt = mouseButtonListener(); 855 return elt; 856} 857 858Element* AccessibilityRenderObject::mouseButtonListener() const 859{ 860 Node* node = m_renderer->node(); 861 if (!node) 862 return 0; 863 864 // check if our parent is a mouse button listener 865 while (node && !node->isElementNode()) 866 node = node->parentNode(); 867 868 if (!node) 869 return 0; 870 871 // FIXME: Do the continuation search like anchorElement does 872 for (Element* element = static_cast<Element*>(node); element; element = element->parentElement()) { 873 if (element->getAttributeEventListener(eventNames().clickEvent) || element->getAttributeEventListener(eventNames().mousedownEvent) || element->getAttributeEventListener(eventNames().mouseupEvent)) 874 return element; 875 } 876 877 return 0; 878} 879 880void AccessibilityRenderObject::increment() 881{ 882 if (roleValue() != SliderRole) 883 return; 884 885 changeValueByPercent(5); 886} 887 888void AccessibilityRenderObject::decrement() 889{ 890 if (roleValue() != SliderRole) 891 return; 892 893 changeValueByPercent(-5); 894} 895 896static Element* siblingWithAriaRole(String role, Node* node) 897{ 898 Node* sibling = node->parentNode()->firstChild(); 899 while (sibling) { 900 if (sibling->isElementNode()) { 901 const AtomicString& siblingAriaRole = static_cast<Element*>(sibling)->getAttribute(roleAttr); 902 if (equalIgnoringCase(siblingAriaRole, role)) 903 return static_cast<Element*>(sibling); 904 } 905 sibling = sibling->nextSibling(); 906 } 907 908 return 0; 909} 910 911Element* AccessibilityRenderObject::menuElementForMenuButton() const 912{ 913 if (ariaRoleAttribute() != MenuButtonRole) 914 return 0; 915 916 return siblingWithAriaRole("menu", renderer()->node()); 917} 918 919AccessibilityObject* AccessibilityRenderObject::menuForMenuButton() const 920{ 921 Element* menu = menuElementForMenuButton(); 922 if (menu && menu->renderer()) 923 return axObjectCache()->getOrCreate(menu->renderer()); 924 return 0; 925} 926 927Element* AccessibilityRenderObject::menuItemElementForMenu() const 928{ 929 if (ariaRoleAttribute() != MenuRole) 930 return 0; 931 932 return siblingWithAriaRole("menuitem", renderer()->node()); 933} 934 935AccessibilityObject* AccessibilityRenderObject::menuButtonForMenu() const 936{ 937 Element* menuItem = menuItemElementForMenu(); 938 939 if (menuItem && menuItem->renderer()) { 940 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem 941 AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem->renderer()); 942 if (menuItemAX->isMenuButton()) 943 return menuItemAX; 944 } 945 return 0; 946} 947 948String AccessibilityRenderObject::helpText() const 949{ 950 if (!m_renderer) 951 return String(); 952 953 const AtomicString& ariaHelp = getAttribute(aria_helpAttr); 954 if (!ariaHelp.isEmpty()) 955 return ariaHelp; 956 957 for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { 958 if (curr->node() && curr->node()->isHTMLElement()) { 959 const AtomicString& summary = static_cast<Element*>(curr->node())->getAttribute(summaryAttr); 960 if (!summary.isEmpty()) 961 return summary; 962 const AtomicString& title = static_cast<Element*>(curr->node())->getAttribute(titleAttr); 963 if (!title.isEmpty()) 964 return title; 965 } 966 967 // Only take help text from an ancestor element if its a group or an unknown role. If help was 968 // added to those kinds of elements, it is likely it was meant for a child element. 969 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); 970 if (axObj) { 971 AccessibilityRole role = axObj->roleValue(); 972 if (role != GroupRole && role != UnknownRole) 973 break; 974 } 975 } 976 977 return String(); 978} 979 980unsigned AccessibilityRenderObject::hierarchicalLevel() const 981{ 982 if (!m_renderer) 983 return 0; 984 985 Node* node = m_renderer->node(); 986 if (!node || !node->isElementNode()) 987 return 0; 988 Element* element = static_cast<Element*>(node); 989 String ariaLevel = element->getAttribute(aria_levelAttr); 990 if (!ariaLevel.isEmpty()) 991 return ariaLevel.toInt(); 992 993 // Only tree item will calculate its level through the DOM currently. 994 if (roleValue() != TreeItemRole) 995 return 0; 996 997 // Hierarchy leveling starts at 0. 998 // We measure tree hierarchy by the number of groups that the item is within. 999 unsigned level = 0; 1000 AccessibilityObject* parent = parentObject(); 1001 while (parent) { 1002 AccessibilityRole parentRole = parent->roleValue(); 1003 if (parentRole == GroupRole) 1004 level++; 1005 else if (parentRole == TreeRole) 1006 break; 1007 1008 parent = parent->parentObject(); 1009 } 1010 1011 return level; 1012} 1013 1014static TextIteratorBehavior textIteratorBehaviorForTextRange() 1015{ 1016 TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility; 1017 1018#if PLATFORM(GTK) 1019 // We need to emit replaced elements for GTK, and present 1020 // them with the 'object replacement character' (0xFFFC). 1021 behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters); 1022#endif 1023 1024 return behavior; 1025} 1026 1027String AccessibilityRenderObject::textUnderElement() const 1028{ 1029 if (!m_renderer) 1030 return String(); 1031 1032 if (isFileUploadButton()) 1033 return toRenderFileUploadControl(m_renderer)->buttonValue(); 1034 1035 Node* node = m_renderer->node(); 1036 if (node) { 1037 if (Frame* frame = node->document()->frame()) { 1038 // catch stale WebCoreAXObject (see <rdar://problem/3960196>) 1039 if (frame->document() != node->document()) 1040 return String(); 1041 1042 return plainText(rangeOfContents(node).get(), textIteratorBehaviorForTextRange()); 1043 } 1044 } 1045 1046 // Sometimes text fragments don't have Node's associated with them (like when 1047 // CSS content is used to insert text). 1048 if (m_renderer->isText()) { 1049 RenderText* renderTextObject = toRenderText(m_renderer); 1050 if (renderTextObject->isTextFragment()) 1051 return String(static_cast<RenderTextFragment*>(m_renderer)->contentString()); 1052 } 1053 1054 // return the null string for anonymous text because it is non-trivial to get 1055 // the actual text and, so far, that is not needed 1056 return String(); 1057} 1058 1059Node* AccessibilityRenderObject::node() const 1060{ 1061 return m_renderer ? m_renderer->node() : 0; 1062} 1063 1064AccessibilityButtonState AccessibilityRenderObject::checkboxOrRadioValue() const 1065{ 1066 if (isNativeCheckboxOrRadio()) 1067 return isChecked() ? ButtonStateOn : ButtonStateOff; 1068 1069 return AccessibilityObject::checkboxOrRadioValue(); 1070} 1071 1072String AccessibilityRenderObject::valueDescription() const 1073{ 1074 // Only sliders and progress bars support value descriptions currently. 1075 if (!isProgressIndicator() && !isSlider()) 1076 return String(); 1077 1078 return getAttribute(aria_valuetextAttr).string(); 1079} 1080 1081float AccessibilityRenderObject::valueForRange() const 1082{ 1083 if (!isProgressIndicator() && !isSlider() && !isScrollbar()) 1084 return 0.0f; 1085 1086 return getAttribute(aria_valuenowAttr).toFloat(); 1087} 1088 1089float AccessibilityRenderObject::maxValueForRange() const 1090{ 1091 if (!isProgressIndicator() && !isSlider()) 1092 return 0.0f; 1093 1094 return getAttribute(aria_valuemaxAttr).toFloat(); 1095} 1096 1097float AccessibilityRenderObject::minValueForRange() const 1098{ 1099 if (!isProgressIndicator() && !isSlider()) 1100 return 0.0f; 1101 1102 return getAttribute(aria_valueminAttr).toFloat(); 1103} 1104 1105String AccessibilityRenderObject::stringValue() const 1106{ 1107 if (!m_renderer || isPasswordField()) 1108 return String(); 1109 1110 RenderBoxModelObject* cssBox = renderBoxModelObject(); 1111 1112 if (ariaRoleAttribute() == StaticTextRole) { 1113 String staticText = text(); 1114 if (!staticText.length()) 1115 staticText = textUnderElement(); 1116 return staticText; 1117 } 1118 1119 if (m_renderer->isText()) 1120 return textUnderElement(); 1121 1122 if (cssBox && cssBox->isMenuList()) { 1123 // RenderMenuList will go straight to the text() of its selected item. 1124 // This has to be overriden in the case where the selected item has an aria label 1125 SelectElement* selectNode = toSelectElement(static_cast<Element*>(m_renderer->node())); 1126 int selectedIndex = selectNode->selectedIndex(); 1127 const Vector<Element*> listItems = selectNode->listItems(); 1128 1129 Element* selectedOption = 0; 1130 if (selectedIndex >= 0 && selectedIndex < (int)listItems.size()) 1131 selectedOption = listItems[selectedIndex]; 1132 if (selectedOption) { 1133 String overridenDescription = selectedOption->getAttribute(aria_labelAttr); 1134 if (!overridenDescription.isNull()) 1135 return overridenDescription; 1136 } 1137 1138 return toRenderMenuList(m_renderer)->text(); 1139 } 1140 1141 if (m_renderer->isListMarker()) 1142 return toRenderListMarker(m_renderer)->text(); 1143 1144 if (cssBox && cssBox->isRenderButton()) 1145 return toRenderButton(m_renderer)->text(); 1146 1147 if (isWebArea()) { 1148 if (m_renderer->frame()) 1149 return String(); 1150 1151 // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here 1152 VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0); 1153 VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX); 1154 if (startVisiblePosition.isNull() || endVisiblePosition.isNull()) 1155 return String(); 1156 1157 return plainText(makeRange(startVisiblePosition, endVisiblePosition).get(), 1158 textIteratorBehaviorForTextRange()); 1159 } 1160 1161 if (isTextControl()) 1162 return text(); 1163 1164 if (isFileUploadButton()) 1165 return toRenderFileUploadControl(m_renderer)->fileTextValue(); 1166 1167 // FIXME: We might need to implement a value here for more types 1168 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; 1169 // this would require subclassing or making accessibilityAttributeNames do something other than return a 1170 // single static array. 1171 return String(); 1172} 1173 1174// This function implements the ARIA accessible name as described by the Mozilla 1175// ARIA Implementer's Guide. 1176static String accessibleNameForNode(Node* node) 1177{ 1178 if (node->isTextNode()) 1179 return static_cast<Text*>(node)->data(); 1180 1181 if (node->hasTagName(inputTag)) 1182 return static_cast<HTMLInputElement*>(node)->value(); 1183 1184 if (node->isHTMLElement()) { 1185 const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr); 1186 if (!alt.isEmpty()) 1187 return alt; 1188 } 1189 1190 return String(); 1191} 1192 1193String AccessibilityRenderObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const 1194{ 1195 StringBuilder builder; 1196 unsigned size = elements.size(); 1197 for (unsigned i = 0; i < size; ++i) { 1198 Element* idElement = elements[i]; 1199 1200 builder.append(accessibleNameForNode(idElement)); 1201 for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement)) 1202 builder.append(accessibleNameForNode(n)); 1203 1204 if (i != size - 1) 1205 builder.append(' '); 1206 } 1207 return builder.toString(); 1208} 1209 1210void AccessibilityRenderObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const 1211{ 1212 Node* node = m_renderer->node(); 1213 if (!node || !node->isElementNode()) 1214 return; 1215 1216 Document* document = m_renderer->document(); 1217 if (!document) 1218 return; 1219 1220 String idList = getAttribute(attribute).string(); 1221 if (idList.isEmpty()) 1222 return; 1223 1224 idList.replace('\n', ' '); 1225 Vector<String> idVector; 1226 idList.split(' ', idVector); 1227 1228 unsigned size = idVector.size(); 1229 for (unsigned i = 0; i < size; ++i) { 1230 String idName = idVector[i]; 1231 Element* idElement = document->getElementById(idName); 1232 if (idElement) 1233 elements.append(idElement); 1234 } 1235} 1236 1237void AccessibilityRenderObject::ariaLabeledByElements(Vector<Element*>& elements) const 1238{ 1239 elementsFromAttribute(elements, aria_labeledbyAttr); 1240 if (!elements.size()) 1241 elementsFromAttribute(elements, aria_labelledbyAttr); 1242} 1243 1244String AccessibilityRenderObject::ariaLabeledByAttribute() const 1245{ 1246 Vector<Element*> elements; 1247 ariaLabeledByElements(elements); 1248 1249 return accessibilityDescriptionForElements(elements); 1250} 1251 1252static HTMLLabelElement* labelForElement(Element* element) 1253{ 1254 RefPtr<NodeList> list = element->document()->getElementsByTagName("label"); 1255 unsigned len = list->length(); 1256 for (unsigned i = 0; i < len; i++) { 1257 if (list->item(i)->hasTagName(labelTag)) { 1258 HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i)); 1259 if (label->control() == element) 1260 return label; 1261 } 1262 } 1263 1264 return 0; 1265} 1266 1267HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const 1268{ 1269 if (!m_renderer) 1270 return 0; 1271 1272 // the control element should not be considered part of the label 1273 if (isControl()) 1274 return 0; 1275 1276 // find if this has a parent that is a label 1277 for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) { 1278 if (parentNode->hasTagName(labelTag)) 1279 return static_cast<HTMLLabelElement*>(parentNode); 1280 } 1281 1282 return 0; 1283} 1284 1285String AccessibilityRenderObject::title() const 1286{ 1287 AccessibilityRole ariaRole = ariaRoleAttribute(); 1288 1289 if (!m_renderer) 1290 return String(); 1291 1292 Node* node = m_renderer->node(); 1293 if (!node) 1294 return String(); 1295 1296 String ariaLabel = ariaLabeledByAttribute(); 1297 if (!ariaLabel.isEmpty()) 1298 return ariaLabel; 1299 1300 const AtomicString& title = getAttribute(titleAttr); 1301 if (!title.isEmpty()) 1302 return title; 1303 1304 bool isInputTag = node->hasTagName(inputTag); 1305 if (isInputTag) { 1306 HTMLInputElement* input = static_cast<HTMLInputElement*>(node); 1307 if (input->isTextButton()) 1308 return input->value(); 1309 } 1310 1311 if (isInputTag || AccessibilityObject::isARIAInput(ariaRole) || isControl()) { 1312 HTMLLabelElement* label = labelForElement(static_cast<Element*>(node)); 1313 if (label && !titleUIElement()) 1314 return label->innerText(); 1315 } 1316 1317 if (roleValue() == ButtonRole 1318 || ariaRole == ListBoxOptionRole 1319 || ariaRole == MenuItemRole 1320 || ariaRole == MenuButtonRole 1321 || ariaRole == RadioButtonRole 1322 || ariaRole == CheckBoxRole 1323 || ariaRole == TabRole 1324 || ariaRole == PopUpButtonRole 1325 || isHeading() 1326 || isLink()) 1327 return textUnderElement(); 1328 1329 return String(); 1330} 1331 1332String AccessibilityRenderObject::ariaDescribedByAttribute() const 1333{ 1334 Vector<Element*> elements; 1335 elementsFromAttribute(elements, aria_describedbyAttr); 1336 1337 return accessibilityDescriptionForElements(elements); 1338} 1339 1340String AccessibilityRenderObject::ariaAccessibilityDescription() const 1341{ 1342 const AtomicString& ariaLabel = getAttribute(aria_labelAttr); 1343 if (!ariaLabel.isEmpty()) 1344 return ariaLabel; 1345 1346 String ariaDescription = ariaDescribedByAttribute(); 1347 if (!ariaDescription.isEmpty()) 1348 return ariaDescription; 1349 1350 return String(); 1351} 1352 1353String AccessibilityRenderObject::accessibilityDescription() const 1354{ 1355 if (!m_renderer) 1356 return String(); 1357 1358 // Static text should not have a description, it should only have a stringValue. 1359 if (roleValue() == StaticTextRole) 1360 return String(); 1361 1362 String ariaDescription = ariaAccessibilityDescription(); 1363 if (!ariaDescription.isEmpty()) 1364 return ariaDescription; 1365 1366 Node* node = m_renderer->node(); 1367 if (isImage() || isInputImage() || isNativeImage()) { 1368 if (node && node->isHTMLElement()) { 1369 const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr); 1370 if (alt.isEmpty()) 1371 return String(); 1372 return alt; 1373 } 1374 } 1375 1376#if ENABLE(MATHML) 1377 if (node && node->isElementNode() && static_cast<Element*>(node)->isMathMLElement()) 1378 return getAttribute(MathMLNames::alttextAttr); 1379#endif 1380 1381 if (isWebArea()) { 1382 Document* document = m_renderer->document(); 1383 1384 // Check if the HTML element has an aria-label for the webpage. 1385 Element* documentElement = document->documentElement(); 1386 if (documentElement) { 1387 const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr); 1388 if (!ariaLabel.isEmpty()) 1389 return ariaLabel; 1390 } 1391 1392 Node* owner = document->ownerElement(); 1393 if (owner) { 1394 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { 1395 const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr); 1396 if (!title.isEmpty()) 1397 return title; 1398 return static_cast<HTMLFrameElementBase*>(owner)->getAttribute(nameAttr); 1399 } 1400 if (owner->isHTMLElement()) 1401 return toHTMLElement(owner)->getAttribute(nameAttr); 1402 } 1403 owner = document->body(); 1404 if (owner && owner->isHTMLElement()) 1405 return toHTMLElement(owner)->getAttribute(nameAttr); 1406 } 1407 1408 return String(); 1409} 1410 1411IntRect AccessibilityRenderObject::boundingBoxRect() const 1412{ 1413 RenderObject* obj = m_renderer; 1414 1415 if (!obj) 1416 return IntRect(); 1417 1418 if (obj->node()) // If we are a continuation, we want to make sure to use the primary renderer. 1419 obj = obj->node()->renderer(); 1420 1421 // absoluteFocusRingQuads will query the hierarchy below this element, which for large webpages can be very slow. 1422 // For a web area, which will have the most elements of any element, absoluteQuads should be used. 1423 Vector<FloatQuad> quads; 1424 if (obj->isText()) 1425 toRenderText(obj)->absoluteQuads(quads, RenderText::ClipToEllipsis); 1426 else if (isWebArea()) 1427 obj->absoluteQuads(quads); 1428 else 1429 obj->absoluteFocusRingQuads(quads); 1430 const size_t n = quads.size(); 1431 if (!n) 1432 return IntRect(); 1433 1434 IntRect result; 1435 for (size_t i = 0; i < n; ++i) { 1436 IntRect r = quads[i].enclosingBoundingBox(); 1437 if (!r.isEmpty()) { 1438 if (obj->style()->hasAppearance()) 1439 obj->theme()->adjustRepaintRect(obj, r); 1440 result.unite(r); 1441 } 1442 } 1443 1444 // The size of the web area should be the content size, not the clipped size. 1445 if (isWebArea() && obj->frame()->view()) 1446 result.setSize(obj->frame()->view()->contentsSize()); 1447 1448 return result; 1449} 1450 1451IntRect AccessibilityRenderObject::checkboxOrRadioRect() const 1452{ 1453 if (!m_renderer) 1454 return IntRect(); 1455 1456 HTMLLabelElement* label = labelForElement(static_cast<Element*>(m_renderer->node())); 1457 if (!label || !label->renderer()) 1458 return boundingBoxRect(); 1459 1460 IntRect labelRect = axObjectCache()->getOrCreate(label->renderer())->elementRect(); 1461 labelRect.unite(boundingBoxRect()); 1462 return labelRect; 1463} 1464 1465IntRect AccessibilityRenderObject::elementRect() const 1466{ 1467 // a checkbox or radio button should encompass its label 1468 if (isCheckboxOrRadio()) 1469 return checkboxOrRadioRect(); 1470 1471 return boundingBoxRect(); 1472} 1473 1474IntSize AccessibilityRenderObject::size() const 1475{ 1476 IntRect rect = elementRect(); 1477 return rect.size(); 1478} 1479 1480IntPoint AccessibilityRenderObject::clickPoint() const 1481{ 1482 // use the default position unless this is an editable web area, in which case we use the selection bounds. 1483 if (!isWebArea() || isReadOnly()) 1484 return AccessibilityObject::clickPoint(); 1485 1486 VisibleSelection visSelection = selection(); 1487 VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd()); 1488 IntRect bounds = boundsForVisiblePositionRange(range); 1489#if PLATFORM(MAC) 1490 bounds.setLocation(m_renderer->document()->view()->screenToContents(bounds.location())); 1491#endif 1492 return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2)); 1493} 1494 1495AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const 1496{ 1497 Element* element = anchorElement(); 1498 if (!element) 1499 return 0; 1500 1501 // Right now, we do not support ARIA links as internal link elements 1502 if (!element->hasTagName(aTag)) 1503 return 0; 1504 HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(element); 1505 1506 KURL linkURL = anchor->href(); 1507 String fragmentIdentifier = linkURL.fragmentIdentifier(); 1508 if (fragmentIdentifier.isEmpty()) 1509 return 0; 1510 1511 // check if URL is the same as current URL 1512 KURL documentURL = m_renderer->document()->url(); 1513 if (!equalIgnoringFragmentIdentifier(documentURL, linkURL)) 1514 return 0; 1515 1516 Node* linkedNode = m_renderer->document()->findAnchor(fragmentIdentifier); 1517 if (!linkedNode) 1518 return 0; 1519 1520 // The element we find may not be accessible, so find the first accessible object. 1521 return firstAccessibleObjectFromNode(linkedNode); 1522} 1523 1524ESpeak AccessibilityRenderObject::speakProperty() const 1525{ 1526 if (!m_renderer) 1527 return AccessibilityObject::speakProperty(); 1528 1529 return m_renderer->style()->speak(); 1530} 1531 1532void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const 1533{ 1534 if (!m_renderer || roleValue() != RadioButtonRole) 1535 return; 1536 1537 Node* node = m_renderer->node(); 1538 if (!node || !node->hasTagName(inputTag)) 1539 return; 1540 1541 HTMLInputElement* input = static_cast<HTMLInputElement*>(node); 1542 // if there's a form, then this is easy 1543 if (input->form()) { 1544 Vector<RefPtr<Node> > formElements; 1545 input->form()->getNamedElements(input->name(), formElements); 1546 1547 unsigned len = formElements.size(); 1548 for (unsigned i = 0; i < len; ++i) { 1549 Node* associateElement = formElements[i].get(); 1550 if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement->renderer())) 1551 linkedUIElements.append(object); 1552 } 1553 } else { 1554 RefPtr<NodeList> list = node->document()->getElementsByTagName("input"); 1555 unsigned len = list->length(); 1556 for (unsigned i = 0; i < len; ++i) { 1557 if (list->item(i)->hasTagName(inputTag)) { 1558 HTMLInputElement* associateElement = static_cast<HTMLInputElement*>(list->item(i)); 1559 if (associateElement->isRadioButton() && associateElement->name() == input->name()) { 1560 if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement->renderer())) 1561 linkedUIElements.append(object); 1562 } 1563 } 1564 } 1565 } 1566} 1567 1568// linked ui elements could be all the related radio buttons in a group 1569// or an internal anchor connection 1570void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const 1571{ 1572 ariaFlowToElements(linkedUIElements); 1573 1574 if (isAnchor()) { 1575 AccessibilityObject* linkedAXElement = internalLinkElement(); 1576 if (linkedAXElement) 1577 linkedUIElements.append(linkedAXElement); 1578 } 1579 1580 if (roleValue() == RadioButtonRole) 1581 addRadioButtonGroupMembers(linkedUIElements); 1582} 1583 1584bool AccessibilityRenderObject::hasTextAlternative() const 1585{ 1586 // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should 1587 // override the "label" element association. 1588 if (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).isEmpty()) 1589 return true; 1590 1591 return false; 1592} 1593 1594bool AccessibilityRenderObject::ariaHasPopup() const 1595{ 1596 return elementAttributeValue(aria_haspopupAttr); 1597} 1598 1599bool AccessibilityRenderObject::supportsARIAFlowTo() const 1600{ 1601 return !getAttribute(aria_flowtoAttr).isEmpty(); 1602} 1603 1604void AccessibilityRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const 1605{ 1606 Vector<Element*> elements; 1607 elementsFromAttribute(elements, aria_flowtoAttr); 1608 1609 AXObjectCache* cache = axObjectCache(); 1610 unsigned count = elements.size(); 1611 for (unsigned k = 0; k < count; ++k) { 1612 Element* element = elements[k]; 1613 AccessibilityObject* flowToElement = cache->getOrCreate(element->renderer()); 1614 if (flowToElement) 1615 flowTo.append(flowToElement); 1616 } 1617 1618} 1619 1620bool AccessibilityRenderObject::supportsARIADropping() const 1621{ 1622 const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); 1623 return !dropEffect.isEmpty(); 1624} 1625 1626bool AccessibilityRenderObject::supportsARIADragging() const 1627{ 1628 const AtomicString& grabbed = getAttribute(aria_grabbedAttr); 1629 return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "false"); 1630} 1631 1632bool AccessibilityRenderObject::isARIAGrabbed() 1633{ 1634 return elementAttributeValue(aria_grabbedAttr); 1635} 1636 1637void AccessibilityRenderObject::setARIAGrabbed(bool grabbed) 1638{ 1639 setElementAttributeValue(aria_grabbedAttr, grabbed); 1640} 1641 1642void AccessibilityRenderObject::determineARIADropEffects(Vector<String>& effects) 1643{ 1644 const AtomicString& dropEffects = getAttribute(aria_dropeffectAttr); 1645 if (dropEffects.isEmpty()) { 1646 effects.clear(); 1647 return; 1648 } 1649 1650 String dropEffectsString = dropEffects.string(); 1651 dropEffectsString.replace('\n', ' '); 1652 dropEffectsString.split(' ', effects); 1653} 1654 1655bool AccessibilityRenderObject::exposesTitleUIElement() const 1656{ 1657 if (!isControl()) 1658 return false; 1659 1660 // checkbox or radio buttons don't expose the title ui element unless it has a title already 1661 if (isCheckboxOrRadio() && getAttribute(titleAttr).isEmpty()) 1662 return false; 1663 1664 if (hasTextAlternative()) 1665 return false; 1666 1667 return true; 1668} 1669 1670AccessibilityObject* AccessibilityRenderObject::titleUIElement() const 1671{ 1672 if (!m_renderer) 1673 return 0; 1674 1675 // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset 1676 if (isFieldset()) 1677 return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend()); 1678 1679 if (!exposesTitleUIElement()) 1680 return 0; 1681 1682 Node* element = m_renderer->node(); 1683 HTMLLabelElement* label = labelForElement(static_cast<Element*>(element)); 1684 if (label && label->renderer()) 1685 return axObjectCache()->getOrCreate(label->renderer()); 1686 1687 return 0; 1688} 1689 1690bool AccessibilityRenderObject::ariaIsHidden() const 1691{ 1692 if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "true")) 1693 return true; 1694 1695 // aria-hidden hides this object and any children 1696 AccessibilityObject* object = parentObject(); 1697 while (object) { 1698 if (object->isAccessibilityRenderObject() && equalIgnoringCase(static_cast<AccessibilityRenderObject*>(object)->getAttribute(aria_hiddenAttr), "true")) 1699 return true; 1700 object = object->parentObject(); 1701 } 1702 1703 return false; 1704} 1705 1706bool AccessibilityRenderObject::isDescendantOfBarrenParent() const 1707{ 1708 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { 1709 if (!object->canHaveChildren()) 1710 return true; 1711 } 1712 1713 return false; 1714} 1715 1716bool AccessibilityRenderObject::isAllowedChildOfTree() const 1717{ 1718 // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline. 1719 AccessibilityObject* axObj = parentObject(); 1720 bool isInTree = false; 1721 while (axObj) { 1722 if (axObj->isTree()) { 1723 isInTree = true; 1724 break; 1725 } 1726 axObj = axObj->parentObject(); 1727 } 1728 1729 // If the object is in a tree, only tree items should be exposed (and the children of tree items). 1730 if (isInTree) { 1731 AccessibilityRole role = roleValue(); 1732 if (role != TreeItemRole && role != StaticTextRole) 1733 return false; 1734 } 1735 return true; 1736} 1737 1738AccessibilityObjectInclusion AccessibilityRenderObject::accessibilityIsIgnoredBase() const 1739{ 1740 // The following cases can apply to any element that's a subclass of AccessibilityRenderObject. 1741 1742 // Ignore invisible elements. 1743 if (!m_renderer || m_renderer->style()->visibility() != VISIBLE) 1744 return IgnoreObject; 1745 1746 // Anything marked as aria-hidden or a child of something aria-hidden must be hidden. 1747 if (ariaIsHidden()) 1748 return IgnoreObject; 1749 1750 // Anything that is a presentational role must be hidden. 1751 if (isPresentationalChildOfAriaRole()) 1752 return IgnoreObject; 1753 1754 // Allow the platform to make a decision. 1755 AccessibilityObjectInclusion decision = accessibilityPlatformIncludesObject(); 1756 if (decision == IncludeObject) 1757 return IncludeObject; 1758 if (decision == IgnoreObject) 1759 return IgnoreObject; 1760 1761 return DefaultBehavior; 1762} 1763 1764bool AccessibilityRenderObject::accessibilityIsIgnored() const 1765{ 1766 // Check first if any of the common reasons cause this element to be ignored. 1767 // Then process other use cases that need to be applied to all the various roles 1768 // that AccessibilityRenderObjects take on. 1769 AccessibilityObjectInclusion decision = accessibilityIsIgnoredBase(); 1770 if (decision == IncludeObject) 1771 return false; 1772 if (decision == IgnoreObject) 1773 return true; 1774 1775 // If this element is within a parent that cannot have children, it should not be exposed. 1776 if (isDescendantOfBarrenParent()) 1777 return true; 1778 1779 if (roleValue() == IgnoredRole) 1780 return true; 1781 1782 if (roleValue() == PresentationalRole || inheritsPresentationalRole()) 1783 return true; 1784 1785 // An ARIA tree can only have tree items and static text as children. 1786 if (!isAllowedChildOfTree()) 1787 return true; 1788 1789 // Allow the platform to decide if the attachment is ignored or not. 1790 if (isAttachment()) 1791 return accessibilityIgnoreAttachment(); 1792 1793 // ignore popup menu items because AppKit does 1794 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { 1795 if (parent->isBoxModelObject() && toRenderBoxModelObject(parent)->isMenuList()) 1796 return true; 1797 } 1798 1799 // find out if this element is inside of a label element. 1800 // if so, it may be ignored because it's the label for a checkbox or radio button 1801 AccessibilityObject* controlObject = correspondingControlForLabelElement(); 1802 if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio()) 1803 return true; 1804 1805 // NOTE: BRs always have text boxes now, so the text box check here can be removed 1806 if (m_renderer->isText()) { 1807 // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level 1808 if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole 1809 || parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole) 1810 return true; 1811 RenderText* renderText = toRenderText(m_renderer); 1812 if (m_renderer->isBR() || !renderText->firstTextBox()) 1813 return true; 1814 1815 // static text beneath TextControls is reported along with the text control text so it's ignored. 1816 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { 1817 if (parent->roleValue() == TextFieldRole) 1818 return true; 1819 } 1820 1821 // text elements that are just empty whitespace should not be returned 1822 return renderText->text()->containsOnlyWhitespace(); 1823 } 1824 1825 if (isHeading()) 1826 return false; 1827 1828 if (isLink()) 1829 return false; 1830 1831 // all controls are accessible 1832 if (isControl()) 1833 return false; 1834 1835 if (ariaRoleAttribute() != UnknownRole) 1836 return false; 1837 1838 // don't ignore labels, because they serve as TitleUIElements 1839 Node* node = m_renderer->node(); 1840 if (node && node->hasTagName(labelTag)) 1841 return false; 1842 1843 // Anything that is content editable should not be ignored. 1844 // However, one cannot just call node->rendererIsEditable() since that will ask if its parents 1845 // are also editable. Only the top level content editable region should be exposed. 1846 if (node && node->isElementNode()) { 1847 Element* element = static_cast<Element*>(node); 1848 const AtomicString& contentEditable = element->getAttribute(contenteditableAttr); 1849 if (equalIgnoringCase(contentEditable, "true")) 1850 return false; 1851 } 1852 1853 // List items play an important role in defining the structure of lists. They should not be ignored. 1854 if (roleValue() == ListItemRole) 1855 return false; 1856 1857 // if this element has aria attributes on it, it should not be ignored. 1858 if (supportsARIAAttributes()) 1859 return false; 1860 1861 if (m_renderer->isBlockFlow() && m_renderer->childrenInline()) 1862 return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener(); 1863 1864 // ignore images seemingly used as spacers 1865 if (isImage()) { 1866 if (node && node->isElementNode()) { 1867 Element* elt = static_cast<Element*>(node); 1868 const AtomicString& alt = elt->getAttribute(altAttr); 1869 // don't ignore an image that has an alt tag 1870 if (!alt.isEmpty()) 1871 return false; 1872 // informal standard is to ignore images with zero-length alt strings 1873 if (!alt.isNull()) 1874 return true; 1875 } 1876 1877 if (node && node->hasTagName(canvasTag)) { 1878 RenderHTMLCanvas* canvas = toRenderHTMLCanvas(m_renderer); 1879 if (canvas->height() <= 1 || canvas->width() <= 1) 1880 return true; 1881 return false; 1882 } 1883 1884 if (isNativeImage()) { 1885 // check for one-dimensional image 1886 RenderImage* image = toRenderImage(m_renderer); 1887 if (image->height() <= 1 || image->width() <= 1) 1888 return true; 1889 1890 // check whether rendered image was stretched from one-dimensional file image 1891 if (image->cachedImage()) { 1892 IntSize imageSize = image->cachedImage()->imageSize(image->view()->zoomFactor()); 1893 return imageSize.height() <= 1 || imageSize.width() <= 1; 1894 } 1895 } 1896 return false; 1897 } 1898 1899 if (isWebArea() || m_renderer->isListMarker()) 1900 return false; 1901 1902 // Using the help text to decide an element's visibility is not as definitive 1903 // as previous checks, so this should remain as one of the last. 1904 if (!helpText().isEmpty()) 1905 return false; 1906 1907 // By default, objects should be ignored so that the AX hierarchy is not 1908 // filled with unnecessary items. 1909 return true; 1910} 1911 1912bool AccessibilityRenderObject::isLoaded() const 1913{ 1914 return !m_renderer->document()->parser(); 1915} 1916 1917double AccessibilityRenderObject::estimatedLoadingProgress() const 1918{ 1919 if (!m_renderer) 1920 return 0; 1921 1922 if (isLoaded()) 1923 return 1.0; 1924 1925 Page* page = m_renderer->document()->page(); 1926 if (!page) 1927 return 0; 1928 1929 return page->progress()->estimatedProgress(); 1930} 1931 1932int AccessibilityRenderObject::layoutCount() const 1933{ 1934 if (!m_renderer->isRenderView()) 1935 return 0; 1936 return toRenderView(m_renderer)->frameView()->layoutCount(); 1937} 1938 1939String AccessibilityRenderObject::text() const 1940{ 1941 // If this is a user defined static text, use the accessible name computation. 1942 if (ariaRoleAttribute() == StaticTextRole) 1943 return ariaAccessibilityDescription(); 1944 1945 if (!isTextControl() || isPasswordField()) 1946 return String(); 1947 1948 if (isNativeTextControl()) 1949 return toRenderTextControl(m_renderer)->text(); 1950 1951 Node* node = m_renderer->node(); 1952 if (!node) 1953 return String(); 1954 if (!node->isElementNode()) 1955 return String(); 1956 1957 return static_cast<Element*>(node)->innerText(); 1958} 1959 1960int AccessibilityRenderObject::textLength() const 1961{ 1962 ASSERT(isTextControl()); 1963 1964 if (isPasswordField()) 1965 return -1; // need to return something distinct from 0 1966 1967 return text().length(); 1968} 1969 1970PlainTextRange AccessibilityRenderObject::ariaSelectedTextRange() const 1971{ 1972 Node* node = m_renderer->node(); 1973 if (!node) 1974 return PlainTextRange(); 1975 1976 ExceptionCode ec = 0; 1977 VisibleSelection visibleSelection = selection(); 1978 RefPtr<Range> currentSelectionRange = visibleSelection.toNormalizedRange(); 1979 if (!currentSelectionRange || !currentSelectionRange->intersectsNode(node, ec)) 1980 return PlainTextRange(); 1981 1982 int start = indexForVisiblePosition(visibleSelection.start()); 1983 int end = indexForVisiblePosition(visibleSelection.end()); 1984 1985 return PlainTextRange(start, end - start); 1986} 1987 1988String AccessibilityRenderObject::selectedText() const 1989{ 1990 ASSERT(isTextControl()); 1991 1992 if (isPasswordField()) 1993 return String(); // need to return something distinct from empty string 1994 1995 if (isNativeTextControl()) { 1996 RenderTextControl* textControl = toRenderTextControl(m_renderer); 1997 return textControl->text().substring(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); 1998 } 1999 2000 if (ariaRoleAttribute() == UnknownRole) 2001 return String(); 2002 2003 return doAXStringForRange(ariaSelectedTextRange()); 2004} 2005 2006const AtomicString& AccessibilityRenderObject::accessKey() const 2007{ 2008 Node* node = m_renderer->node(); 2009 if (!node) 2010 return nullAtom; 2011 if (!node->isElementNode()) 2012 return nullAtom; 2013 return static_cast<Element*>(node)->getAttribute(accesskeyAttr); 2014} 2015 2016VisibleSelection AccessibilityRenderObject::selection() const 2017{ 2018 return m_renderer->frame()->selection()->selection(); 2019} 2020 2021PlainTextRange AccessibilityRenderObject::selectedTextRange() const 2022{ 2023 ASSERT(isTextControl()); 2024 2025 if (isPasswordField()) 2026 return PlainTextRange(); 2027 2028 AccessibilityRole ariaRole = ariaRoleAttribute(); 2029 if (isNativeTextControl() && ariaRole == UnknownRole) { 2030 RenderTextControl* textControl = toRenderTextControl(m_renderer); 2031 return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); 2032 } 2033 2034 if (ariaRole == UnknownRole) 2035 return PlainTextRange(); 2036 2037 return ariaSelectedTextRange(); 2038} 2039 2040void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) 2041{ 2042 if (isNativeTextControl()) { 2043 setSelectionRange(m_renderer->node(), range.start, range.start + range.length); 2044 return; 2045 } 2046 2047 Document* document = m_renderer->document(); 2048 if (!document) 2049 return; 2050 Frame* frame = document->frame(); 2051 if (!frame) 2052 return; 2053 Node* node = m_renderer->node(); 2054 frame->selection()->setSelection(VisibleSelection(Position(node, range.start, Position::PositionIsOffsetInAnchor), 2055 Position(node, range.start + range.length, Position::PositionIsOffsetInAnchor), DOWNSTREAM)); 2056} 2057 2058KURL AccessibilityRenderObject::url() const 2059{ 2060 if (isAnchor() && m_renderer->node()->hasTagName(aTag)) { 2061 if (HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(anchorElement())) 2062 return anchor->href(); 2063 } 2064 2065 if (isWebArea()) 2066 return m_renderer->document()->url(); 2067 2068 if (isImage() && m_renderer->node() && m_renderer->node()->hasTagName(imgTag)) 2069 return static_cast<HTMLImageElement*>(m_renderer->node())->src(); 2070 2071 if (isInputImage()) 2072 return static_cast<HTMLInputElement*>(m_renderer->node())->src(); 2073 2074 return KURL(); 2075} 2076 2077bool AccessibilityRenderObject::isVisited() const 2078{ 2079 // FIXME: Is it a privacy violation to expose visited information to accessibility APIs? 2080 return m_renderer->style()->isLink() && m_renderer->style()->insideLink() == InsideVisitedLink; 2081} 2082 2083void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& attributeName, bool value) 2084{ 2085 if (!m_renderer) 2086 return; 2087 2088 Node* node = m_renderer->node(); 2089 if (!node || !node->isElementNode()) 2090 return; 2091 2092 Element* element = static_cast<Element*>(node); 2093 element->setAttribute(attributeName, (value) ? "true" : "false"); 2094} 2095 2096bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const 2097{ 2098 if (!m_renderer) 2099 return false; 2100 2101 return equalIgnoringCase(getAttribute(attributeName), "true"); 2102} 2103 2104void AccessibilityRenderObject::setIsExpanded(bool isExpanded) 2105{ 2106 // Combo boxes, tree items and rows can be expanded (in different ways on different platforms). 2107 // That action translates into setting the aria-expanded attribute to true. 2108 AccessibilityRole role = roleValue(); 2109 switch (role) { 2110 case ComboBoxRole: 2111 case TreeItemRole: 2112 case RowRole: 2113 setElementAttributeValue(aria_expandedAttr, isExpanded); 2114 break; 2115 default: 2116 break; 2117 } 2118} 2119 2120bool AccessibilityRenderObject::isRequired() const 2121{ 2122 if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true")) 2123 return true; 2124 2125 Node* n = node(); 2126 if (n && (n->isElementNode() && static_cast<Element*>(n)->isFormControlElement())) 2127 return static_cast<HTMLFormControlElement*>(n)->required(); 2128 2129 return false; 2130} 2131 2132bool AccessibilityRenderObject::isSelected() const 2133{ 2134 if (!m_renderer) 2135 return false; 2136 2137 Node* node = m_renderer->node(); 2138 if (!node) 2139 return false; 2140 2141 const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); 2142 if (equalIgnoringCase(ariaSelected, "true")) 2143 return true; 2144 2145 if (isTabItem() && isTabItemSelected()) 2146 return true; 2147 2148 return false; 2149} 2150 2151bool AccessibilityRenderObject::isTabItemSelected() const 2152{ 2153 if (!isTabItem() || !m_renderer) 2154 return false; 2155 2156 Node* node = m_renderer->node(); 2157 if (!node || !node->isElementNode()) 2158 return false; 2159 2160 // The ARIA spec says a tab item can also be selected if it is aria-labeled by a tabpanel 2161 // that has keyboard focus inside of it, or if a tabpanel in its aria-controls list has KB 2162 // focus inside of it. 2163 AccessibilityObject* focusedElement = focusedUIElement(); 2164 if (!focusedElement) 2165 return false; 2166 2167 Vector<Element*> elements; 2168 elementsFromAttribute(elements, aria_controlsAttr); 2169 2170 unsigned count = elements.size(); 2171 for (unsigned k = 0; k < count; ++k) { 2172 Element* element = elements[k]; 2173 AccessibilityObject* tabPanel = axObjectCache()->getOrCreate(element->renderer()); 2174 2175 // A tab item should only control tab panels. 2176 if (!tabPanel || tabPanel->roleValue() != TabPanelRole) 2177 continue; 2178 2179 AccessibilityObject* checkFocusElement = focusedElement; 2180 // Check if the focused element is a descendant of the element controlled by the tab item. 2181 while (checkFocusElement) { 2182 if (tabPanel == checkFocusElement) 2183 return true; 2184 checkFocusElement = checkFocusElement->parentObject(); 2185 } 2186 } 2187 2188 return false; 2189} 2190 2191bool AccessibilityRenderObject::isFocused() const 2192{ 2193 if (!m_renderer) 2194 return false; 2195 2196 Document* document = m_renderer->document(); 2197 if (!document) 2198 return false; 2199 2200 Node* focusedNode = document->focusedNode(); 2201 if (!focusedNode) 2202 return false; 2203 2204 // A web area is represented by the Document node in the DOM tree, which isn't focusable. 2205 // Check instead if the frame's selection controller is focused 2206 if (focusedNode == m_renderer->node() 2207 || (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive())) 2208 return true; 2209 2210 return false; 2211} 2212 2213void AccessibilityRenderObject::setFocused(bool on) 2214{ 2215 if (!canSetFocusAttribute()) 2216 return; 2217 2218 if (!on) 2219 m_renderer->document()->setFocusedNode(0); 2220 else { 2221 if (m_renderer->node()->isElementNode()) 2222 static_cast<Element*>(m_renderer->node())->focus(); 2223 else 2224 m_renderer->document()->setFocusedNode(m_renderer->node()); 2225 } 2226} 2227 2228void AccessibilityRenderObject::changeValueByPercent(float percentChange) 2229{ 2230 float range = maxValueForRange() - minValueForRange(); 2231 float value = valueForRange(); 2232 2233 value += range * (percentChange / 100); 2234 setValue(String::number(value)); 2235 2236 axObjectCache()->postNotification(m_renderer, AXObjectCache::AXValueChanged, true); 2237} 2238 2239void AccessibilityRenderObject::setSelected(bool enabled) 2240{ 2241 setElementAttributeValue(aria_selectedAttr, enabled); 2242} 2243 2244void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) 2245{ 2246 // Setting selected only makes sense in trees and tables (and tree-tables). 2247 AccessibilityRole role = roleValue(); 2248 if (role != TreeRole && role != TreeGridRole && role != TableRole) 2249 return; 2250 2251 bool isMulti = isMultiSelectable(); 2252 unsigned count = selectedRows.size(); 2253 if (count > 1 && !isMulti) 2254 count = 1; 2255 2256 for (unsigned k = 0; k < count; ++k) 2257 selectedRows[k]->setSelected(true); 2258} 2259 2260void AccessibilityRenderObject::setValue(const String& string) 2261{ 2262 if (!m_renderer || !m_renderer->node() || !m_renderer->node()->isElementNode()) 2263 return; 2264 Element* element = static_cast<Element*>(m_renderer->node()); 2265 2266 if (roleValue() == SliderRole) 2267 element->setAttribute(aria_valuenowAttr, string); 2268 2269 if (!m_renderer->isBoxModelObject()) 2270 return; 2271 RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); 2272 2273 // FIXME: Do we want to do anything here for ARIA textboxes? 2274 if (renderer->isTextField()) { 2275 // FIXME: This is not safe! Other elements could have a TextField renderer. 2276 static_cast<HTMLInputElement*>(element)->setValue(string); 2277 } else if (renderer->isTextArea()) { 2278 // FIXME: This is not safe! Other elements could have a TextArea renderer. 2279 static_cast<HTMLTextAreaElement*>(element)->setValue(string); 2280 } 2281} 2282 2283void AccessibilityRenderObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const 2284{ 2285 Vector<Element*> elements; 2286 elementsFromAttribute(elements, aria_ownsAttr); 2287 2288 unsigned count = elements.size(); 2289 for (unsigned k = 0; k < count; ++k) { 2290 RenderObject* render = elements[k]->renderer(); 2291 AccessibilityObject* obj = axObjectCache()->getOrCreate(render); 2292 if (obj) 2293 axObjects.append(obj); 2294 } 2295} 2296 2297bool AccessibilityRenderObject::supportsARIAOwns() const 2298{ 2299 if (!m_renderer) 2300 return false; 2301 const AtomicString& ariaOwns = getAttribute(aria_ownsAttr); 2302 2303 return !ariaOwns.isEmpty(); 2304} 2305 2306bool AccessibilityRenderObject::isEnabled() const 2307{ 2308 ASSERT(m_renderer); 2309 2310 if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true")) 2311 return false; 2312 2313 Node* node = m_renderer->node(); 2314 if (!node || !node->isElementNode()) 2315 return true; 2316 2317 return static_cast<Element*>(node)->isEnabledFormControl(); 2318} 2319 2320RenderView* AccessibilityRenderObject::topRenderer() const 2321{ 2322 return m_renderer->document()->topDocument()->renderView(); 2323} 2324 2325Document* AccessibilityRenderObject::document() const 2326{ 2327 if (!m_renderer) 2328 return 0; 2329 return m_renderer->document(); 2330} 2331 2332FrameView* AccessibilityRenderObject::topDocumentFrameView() const 2333{ 2334 return topRenderer()->view()->frameView(); 2335} 2336 2337Widget* AccessibilityRenderObject::widget() const 2338{ 2339 if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isWidget()) 2340 return 0; 2341 return toRenderWidget(m_renderer)->widget(); 2342} 2343 2344AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const 2345{ 2346 // find an image that is using this map 2347 if (!map) 2348 return 0; 2349 2350 HTMLImageElement* imageElement = map->imageElement(); 2351 if (!imageElement) 2352 return 0; 2353 2354 return axObjectCache()->getOrCreate(imageElement->renderer()); 2355} 2356 2357void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) 2358{ 2359 Document* document = m_renderer->document(); 2360 RefPtr<HTMLCollection> coll = document->links(); 2361 Node* curr = coll->firstItem(); 2362 while (curr) { 2363 RenderObject* obj = curr->renderer(); 2364 if (obj) { 2365 RefPtr<AccessibilityObject> axobj = document->axObjectCache()->getOrCreate(obj); 2366 ASSERT(axobj); 2367 if (!axobj->accessibilityIsIgnored() && axobj->isLink()) 2368 result.append(axobj); 2369 } else { 2370 Node* parent = curr->parentNode(); 2371 if (parent && curr->hasTagName(areaTag) && parent->hasTagName(mapTag)) { 2372 AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole)); 2373 areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(curr)); 2374 areaObject->setHTMLMapElement(static_cast<HTMLMapElement*>(parent)); 2375 areaObject->setParent(accessibilityParentForImageMap(static_cast<HTMLMapElement*>(parent))); 2376 2377 result.append(areaObject); 2378 } 2379 } 2380 curr = coll->nextItem(); 2381 } 2382} 2383 2384FrameView* AccessibilityRenderObject::documentFrameView() const 2385{ 2386 if (!m_renderer || !m_renderer->document()) 2387 return 0; 2388 2389 // this is the RenderObject's Document's Frame's FrameView 2390 return m_renderer->document()->view(); 2391} 2392 2393Widget* AccessibilityRenderObject::widgetForAttachmentView() const 2394{ 2395 if (!isAttachment()) 2396 return 0; 2397 return toRenderWidget(m_renderer)->widget(); 2398} 2399 2400FrameView* AccessibilityRenderObject::frameViewIfRenderView() const 2401{ 2402 if (!m_renderer->isRenderView()) 2403 return 0; 2404 // this is the RenderObject's Document's renderer's FrameView 2405 return m_renderer->view()->frameView(); 2406} 2407 2408// This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns 2409// a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file 2410VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const 2411{ 2412 if (!m_renderer) 2413 return VisiblePositionRange(); 2414 2415 // construct VisiblePositions for start and end 2416 Node* node = m_renderer->node(); 2417 if (!node) 2418 return VisiblePositionRange(); 2419 2420 VisiblePosition startPos = firstPositionInOrBeforeNode(node); 2421 VisiblePosition endPos = lastPositionInOrAfterNode(node); 2422 2423 // the VisiblePositions are equal for nodes like buttons, so adjust for that 2424 // FIXME: Really? [button, 0] and [button, 1] are distinct (before and after the button) 2425 // I expect this code is only hit for things like empty divs? In which case I don't think 2426 // the behavior is correct here -- eseidel 2427 if (startPos == endPos) { 2428 endPos = endPos.next(); 2429 if (endPos.isNull()) 2430 endPos = startPos; 2431 } 2432 2433 return VisiblePositionRange(startPos, endPos); 2434} 2435 2436VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const 2437{ 2438 if (!lineCount || !m_renderer) 2439 return VisiblePositionRange(); 2440 2441 // iterate over the lines 2442 // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the 2443 // last offset of the last line 2444 VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForCoordinates(0, 0); 2445 VisiblePosition savedVisiblePos; 2446 while (--lineCount) { 2447 savedVisiblePos = visiblePos; 2448 visiblePos = nextLinePosition(visiblePos, 0); 2449 if (visiblePos.isNull() || visiblePos == savedVisiblePos) 2450 return VisiblePositionRange(); 2451 } 2452 2453 // make a caret selection for the marker position, then extend it to the line 2454 // NOTE: ignores results of sel.modify because it returns false when 2455 // starting at an empty line. The resulting selection in that case 2456 // will be a caret at visiblePos. 2457 SelectionController selection; 2458 selection.setSelection(VisibleSelection(visiblePos)); 2459 selection.modify(SelectionController::AlterationExtend, DirectionRight, LineBoundary); 2460 2461 return VisiblePositionRange(selection.selection().visibleStart(), selection.selection().visibleEnd()); 2462} 2463 2464VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const 2465{ 2466 if (!m_renderer) 2467 return VisiblePosition(); 2468 2469 if (isNativeTextControl()) 2470 return toRenderTextControl(m_renderer)->visiblePositionForIndex(index); 2471 2472 if (!allowsTextRanges() && !m_renderer->isText()) 2473 return VisiblePosition(); 2474 2475 Node* node = m_renderer->node(); 2476 if (!node) 2477 return VisiblePosition(); 2478 2479 if (index <= 0) 2480 return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); 2481 2482 ExceptionCode ec = 0; 2483 RefPtr<Range> range = Range::create(m_renderer->document()); 2484 range->selectNodeContents(node, ec); 2485 CharacterIterator it(range.get()); 2486 it.advance(index - 1); 2487 return VisiblePosition(Position(it.range()->endContainer(ec), it.range()->endOffset(ec), Position::PositionIsOffsetInAnchor), UPSTREAM); 2488} 2489 2490int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const 2491{ 2492 if (isNativeTextControl()) 2493 return RenderTextControl::indexForVisiblePosition(toRenderTextControl(m_renderer)->innerTextElement(), pos); 2494 2495 if (!isTextControl()) 2496 return 0; 2497 2498 Node* node = m_renderer->node(); 2499 if (!node) 2500 return 0; 2501 2502 Position indexPosition = pos.deepEquivalent(); 2503 if (!indexPosition.anchorNode() || indexPosition.anchorNode()->rootEditableElement() != node) 2504 return 0; 2505 2506 ExceptionCode ec = 0; 2507 RefPtr<Range> range = Range::create(m_renderer->document()); 2508 range->setStart(node, 0, ec); 2509 range->setEnd(indexPosition.anchorNode(), indexPosition.deprecatedEditingOffset(), ec); 2510 2511#if PLATFORM(GTK) 2512 // We need to consider replaced elements for GTK, as they will be 2513 // presented with the 'object replacement character' (0xFFFC). 2514 return TextIterator::rangeLength(range.get(), true); 2515#else 2516 return TextIterator::rangeLength(range.get()); 2517#endif 2518} 2519 2520IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const 2521{ 2522 if (visiblePositionRange.isNull()) 2523 return IntRect(); 2524 2525 // Create a mutable VisiblePositionRange. 2526 VisiblePositionRange range(visiblePositionRange); 2527 IntRect rect1 = range.start.absoluteCaretBounds(); 2528 IntRect rect2 = range.end.absoluteCaretBounds(); 2529 2530 // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds 2531 if (rect2.y() != rect1.y()) { 2532 VisiblePosition endOfFirstLine = endOfLine(range.start); 2533 if (range.start == endOfFirstLine) { 2534 range.start.setAffinity(DOWNSTREAM); 2535 rect1 = range.start.absoluteCaretBounds(); 2536 } 2537 if (range.end == endOfFirstLine) { 2538 range.end.setAffinity(UPSTREAM); 2539 rect2 = range.end.absoluteCaretBounds(); 2540 } 2541 } 2542 2543 IntRect ourrect = rect1; 2544 ourrect.unite(rect2); 2545 2546 // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead 2547 if (rect1.maxY() != rect2.maxY()) { 2548 RefPtr<Range> dataRange = makeRange(range.start, range.end); 2549 IntRect boundingBox = dataRange->boundingBox(); 2550 String rangeString = plainText(dataRange.get()); 2551 if (rangeString.length() > 1 && !boundingBox.isEmpty()) 2552 ourrect = boundingBox; 2553 } 2554 2555#if PLATFORM(MAC) 2556 return m_renderer->document()->view()->contentsToScreen(ourrect); 2557#else 2558 return ourrect; 2559#endif 2560} 2561 2562void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const 2563{ 2564 if (range.start.isNull() || range.end.isNull()) 2565 return; 2566 2567 // make selection and tell the document to use it. if it's zero length, then move to that position 2568 if (range.start == range.end) 2569 m_renderer->frame()->selection()->moveTo(range.start, true); 2570 else { 2571 VisibleSelection newSelection = VisibleSelection(range.start, range.end); 2572 m_renderer->frame()->selection()->setSelection(newSelection); 2573 } 2574} 2575 2576VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const 2577{ 2578 if (!m_renderer) 2579 return VisiblePosition(); 2580 2581 // convert absolute point to view coordinates 2582 FrameView* frameView = m_renderer->document()->topDocument()->renderer()->view()->frameView(); 2583 RenderView* renderView = topRenderer(); 2584 Node* innerNode = 0; 2585 2586 // locate the node containing the point 2587 IntPoint pointResult; 2588 while (1) { 2589 IntPoint ourpoint; 2590#if PLATFORM(MAC) 2591 ourpoint = frameView->screenToContents(point); 2592#else 2593 ourpoint = point; 2594#endif 2595 HitTestRequest request(HitTestRequest::ReadOnly | 2596 HitTestRequest::Active); 2597 HitTestResult result(ourpoint); 2598 renderView->layer()->hitTest(request, result); 2599 innerNode = result.innerNode(); 2600 if (!innerNode) 2601 return VisiblePosition(); 2602 2603 RenderObject* renderer = innerNode->renderer(); 2604 if (!renderer) 2605 return VisiblePosition(); 2606 2607 pointResult = result.localPoint(); 2608 2609 // done if hit something other than a widget 2610 if (!renderer->isWidget()) 2611 break; 2612 2613 // descend into widget (FRAME, IFRAME, OBJECT...) 2614 Widget* widget = toRenderWidget(renderer)->widget(); 2615 if (!widget || !widget->isFrameView()) 2616 break; 2617 Frame* frame = static_cast<FrameView*>(widget)->frame(); 2618 if (!frame) 2619 break; 2620 renderView = frame->document()->renderView(); 2621 frameView = static_cast<FrameView*>(widget); 2622 } 2623 2624 return innerNode->renderer()->positionForPoint(pointResult); 2625} 2626 2627// NOTE: Consider providing this utility method as AX API 2628VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const 2629{ 2630 if (!isTextControl()) 2631 return VisiblePosition(); 2632 2633 // lastIndexOK specifies whether the position after the last character is acceptable 2634 if (indexValue >= text().length()) { 2635 if (!lastIndexOK || indexValue > text().length()) 2636 return VisiblePosition(); 2637 } 2638 VisiblePosition position = visiblePositionForIndex(indexValue); 2639 position.setAffinity(DOWNSTREAM); 2640 return position; 2641} 2642 2643// NOTE: Consider providing this utility method as AX API 2644int AccessibilityRenderObject::index(const VisiblePosition& position) const 2645{ 2646 if (!isTextControl()) 2647 return -1; 2648 2649 Node* node = position.deepEquivalent().deprecatedNode(); 2650 if (!node) 2651 return -1; 2652 2653 for (RenderObject* renderer = node->renderer(); renderer && renderer->node(); renderer = renderer->parent()) { 2654 if (renderer == m_renderer) 2655 return indexForVisiblePosition(position); 2656 } 2657 2658 return -1; 2659} 2660 2661// Given a line number, the range of characters of the text associated with this accessibility 2662// object that contains the line number. 2663PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const 2664{ 2665 if (!isTextControl()) 2666 return PlainTextRange(); 2667 2668 // iterate to the specified line 2669 VisiblePosition visiblePos = visiblePositionForIndex(0); 2670 VisiblePosition savedVisiblePos; 2671 for (unsigned lineCount = lineNumber; lineCount; lineCount -= 1) { 2672 savedVisiblePos = visiblePos; 2673 visiblePos = nextLinePosition(visiblePos, 0); 2674 if (visiblePos.isNull() || visiblePos == savedVisiblePos) 2675 return PlainTextRange(); 2676 } 2677 2678 // Get the end of the line based on the starting position. 2679 VisiblePosition endPosition = endOfLine(visiblePos); 2680 2681 int index1 = indexForVisiblePosition(visiblePos); 2682 int index2 = indexForVisiblePosition(endPosition); 2683 2684 // add one to the end index for a line break not caused by soft line wrap (to match AppKit) 2685 if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull()) 2686 index2 += 1; 2687 2688 // return nil rather than an zero-length range (to match AppKit) 2689 if (index1 == index2) 2690 return PlainTextRange(); 2691 2692 return PlainTextRange(index1, index2 - index1); 2693} 2694 2695// The composed character range in the text associated with this accessibility object that 2696// is specified by the given index value. This parameterized attribute returns the complete 2697// range of characters (including surrogate pairs of multi-byte glyphs) at the given index. 2698PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const 2699{ 2700 if (!isTextControl()) 2701 return PlainTextRange(); 2702 2703 String elementText = text(); 2704 if (!elementText.length() || index > elementText.length() - 1) 2705 return PlainTextRange(); 2706 2707 return PlainTextRange(index, 1); 2708} 2709 2710// A substring of the text associated with this accessibility object that is 2711// specified by the given character range. 2712String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const 2713{ 2714 if (isPasswordField()) 2715 return String(); 2716 2717 if (!range.length) 2718 return String(); 2719 2720 if (!isTextControl()) 2721 return String(); 2722 2723 String elementText = text(); 2724 if (range.start + range.length > elementText.length()) 2725 return String(); 2726 2727 return elementText.substring(range.start, range.length); 2728} 2729 2730// The bounding rectangle of the text associated with this accessibility object that is 2731// specified by the given range. This is the bounding rectangle a sighted user would see 2732// on the display screen, in pixels. 2733IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const 2734{ 2735 if (allowsTextRanges()) 2736 return boundsForVisiblePositionRange(visiblePositionRangeForRange(range)); 2737 return IntRect(); 2738} 2739 2740AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const 2741{ 2742 if (!area) 2743 return 0; 2744 2745 HTMLMapElement* map = static_cast<HTMLMapElement*>(area->parentNode()); 2746 AccessibilityObject* parent = accessibilityParentForImageMap(map); 2747 if (!parent) 2748 return 0; 2749 2750 AccessibilityObject::AccessibilityChildrenVector children = parent->children(); 2751 2752 unsigned count = children.size(); 2753 for (unsigned k = 0; k < count; ++k) { 2754 if (children[k]->elementRect().contains(point)) 2755 return children[k].get(); 2756 } 2757 2758 return 0; 2759} 2760 2761AccessibilityObject* AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const 2762{ 2763 if (!m_renderer || !m_renderer->hasLayer()) 2764 return 0; 2765 2766 RenderLayer* layer = toRenderBox(m_renderer)->layer(); 2767 2768 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); 2769 HitTestResult hitTestResult = HitTestResult(point); 2770 layer->hitTest(request, hitTestResult); 2771 if (!hitTestResult.innerNode()) 2772 return 0; 2773 Node* node = hitTestResult.innerNode()->shadowAncestorNode(); 2774 2775 if (node->hasTagName(areaTag)) 2776 return accessibilityImageMapHitTest(static_cast<HTMLAreaElement*>(node), point); 2777 2778 if (node->hasTagName(optionTag)) 2779 node = static_cast<HTMLOptionElement*>(node)->ownerSelectElement(); 2780 2781 RenderObject* obj = node->renderer(); 2782 if (!obj) 2783 return 0; 2784 2785 AccessibilityObject* result = obj->document()->axObjectCache()->getOrCreate(obj); 2786 result->updateChildrenIfNecessary(); 2787 2788 // Allow the element to perform any hit-testing it might need to do to reach non-render children. 2789 result = result->elementAccessibilityHitTest(point); 2790 2791 if (result->accessibilityIsIgnored()) { 2792 // If this element is the label of a control, a hit test should return the control. 2793 AccessibilityObject* controlObject = result->correspondingControlForLabelElement(); 2794 if (controlObject && !controlObject->exposesTitleUIElement()) 2795 return controlObject; 2796 2797 result = result->parentObjectUnignored(); 2798 } 2799 2800 return result; 2801} 2802 2803bool AccessibilityRenderObject::shouldFocusActiveDescendant() const 2804{ 2805 switch (ariaRoleAttribute()) { 2806 case GroupRole: 2807 case ComboBoxRole: 2808 case ListBoxRole: 2809 case MenuRole: 2810 case MenuBarRole: 2811 case RadioGroupRole: 2812 case RowRole: 2813 case PopUpButtonRole: 2814 case ProgressIndicatorRole: 2815 case ToolbarRole: 2816 case OutlineRole: 2817 case TreeRole: 2818 case GridRole: 2819 /* FIXME: replace these with actual roles when they are added to AccessibilityRole 2820 composite 2821 alert 2822 alertdialog 2823 status 2824 timer 2825 */ 2826 return true; 2827 default: 2828 return false; 2829 } 2830} 2831 2832AccessibilityObject* AccessibilityRenderObject::activeDescendant() const 2833{ 2834 if (!m_renderer) 2835 return 0; 2836 2837 if (m_renderer->node() && !m_renderer->node()->isElementNode()) 2838 return 0; 2839 Element* element = static_cast<Element*>(m_renderer->node()); 2840 2841 const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr); 2842 if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) 2843 return 0; 2844 2845 Element* target = document()->getElementById(activeDescendantAttrStr); 2846 if (!target) 2847 return 0; 2848 2849 AccessibilityObject* obj = axObjectCache()->getOrCreate(target->renderer()); 2850 if (obj && obj->isAccessibilityRenderObject()) 2851 // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification 2852 return obj; 2853 return 0; 2854} 2855 2856void AccessibilityRenderObject::handleAriaExpandedChanged() 2857{ 2858 // Find if a parent of this object should handle aria-expanded changes. 2859 AccessibilityObject* containerParent = this->parentObject(); 2860 while (containerParent) { 2861 bool foundParent = false; 2862 2863 switch (containerParent->roleValue()) { 2864 case TreeRole: 2865 case TreeGridRole: 2866 case GridRole: 2867 case TableRole: 2868 case BrowserRole: 2869 foundParent = true; 2870 break; 2871 default: 2872 break; 2873 } 2874 2875 if (foundParent) 2876 break; 2877 2878 containerParent = containerParent->parentObject(); 2879 } 2880 2881 // Post that the row count changed. 2882 if (containerParent) 2883 axObjectCache()->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged, true); 2884 2885 // Post that the specific row either collapsed or expanded. 2886 if (roleValue() == RowRole || roleValue() == TreeItemRole) 2887 axObjectCache()->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed, true); 2888} 2889 2890void AccessibilityRenderObject::handleActiveDescendantChanged() 2891{ 2892 Element* element = static_cast<Element*>(renderer()->node()); 2893 if (!element) 2894 return; 2895 Document* doc = renderer()->document(); 2896 if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedNode() != element) 2897 return; 2898 AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant()); 2899 2900 if (activedescendant && shouldFocusActiveDescendant()) 2901 doc->axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged, true); 2902} 2903 2904AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElement() const 2905{ 2906 HTMLLabelElement* labelElement = labelElementContainer(); 2907 if (!labelElement) 2908 return 0; 2909 2910 HTMLElement* correspondingControl = labelElement->control(); 2911 if (!correspondingControl) 2912 return 0; 2913 2914 return axObjectCache()->getOrCreate(correspondingControl->renderer()); 2915} 2916 2917AccessibilityObject* AccessibilityRenderObject::correspondingLabelForControlElement() const 2918{ 2919 if (!m_renderer) 2920 return 0; 2921 2922 Node* node = m_renderer->node(); 2923 if (node && node->isHTMLElement()) { 2924 HTMLLabelElement* label = labelForElement(static_cast<Element*>(node)); 2925 if (label) 2926 return axObjectCache()->getOrCreate(label->renderer()); 2927 } 2928 2929 return 0; 2930} 2931 2932bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject* renderer) const 2933{ 2934 // AX clients will listen for AXValueChange on a text control. 2935 if (renderer->isTextControl()) 2936 return true; 2937 2938 // AX clients will listen for AXSelectedChildrenChanged on listboxes. 2939 Node* node = renderer->node(); 2940 if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListBox())) 2941 return true; 2942 2943 // Textboxes should send out notifications. 2944 if (nodeHasRole(node, "textbox")) 2945 return true; 2946 2947 return false; 2948} 2949 2950AccessibilityObject* AccessibilityRenderObject::observableObject() const 2951{ 2952 // Find the object going up the parent chain that is used in accessibility to monitor certain notifications. 2953 for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) { 2954 if (renderObjectIsObservable(renderer)) 2955 return axObjectCache()->getOrCreate(renderer); 2956 } 2957 2958 return 0; 2959} 2960 2961AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const 2962{ 2963 const AtomicString& ariaRole = getAttribute(roleAttr); 2964 if (ariaRole.isNull() || ariaRole.isEmpty()) 2965 return UnknownRole; 2966 2967 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole); 2968 2969 if (role == ButtonRole && ariaHasPopup()) 2970 role = PopUpButtonRole; 2971 2972 if (role == TextAreaRole && ariaIsMultiline()) 2973 role = TextFieldRole; 2974 2975 if (role) 2976 return role; 2977 2978 AccessibilityObject* parentObject = parentObjectUnignored(); 2979 if (!parentObject) 2980 return UnknownRole; 2981 2982 AccessibilityRole parentAriaRole = parentObject->ariaRoleAttribute(); 2983 2984 // selects and listboxes both have options as child roles, but they map to different roles within WebCore 2985 if (equalIgnoringCase(ariaRole, "option")) { 2986 if (parentAriaRole == MenuRole) 2987 return MenuItemRole; 2988 if (parentAriaRole == ListBoxRole) 2989 return ListBoxOptionRole; 2990 } 2991 // an aria "menuitem" may map to MenuButton or MenuItem depending on its parent 2992 if (equalIgnoringCase(ariaRole, "menuitem")) { 2993 if (parentAriaRole == GroupRole) 2994 return MenuButtonRole; 2995 if (parentAriaRole == MenuRole || parentAriaRole == MenuBarRole) 2996 return MenuItemRole; 2997 } 2998 2999 return UnknownRole; 3000} 3001 3002AccessibilityRole AccessibilityRenderObject::ariaRoleAttribute() const 3003{ 3004 return m_ariaRole; 3005} 3006 3007void AccessibilityRenderObject::updateAccessibilityRole() 3008{ 3009 bool ignoredStatus = accessibilityIsIgnored(); 3010 m_role = determineAccessibilityRole(); 3011 3012 // The AX hierarchy only needs to be updated if the ignored status of an element has changed. 3013 if (ignoredStatus != accessibilityIsIgnored()) 3014 childrenChanged(); 3015} 3016 3017AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() 3018{ 3019 if (!m_renderer) 3020 return UnknownRole; 3021 3022 m_ariaRole = determineAriaRoleAttribute(); 3023 3024 Node* node = m_renderer->node(); 3025 AccessibilityRole ariaRole = ariaRoleAttribute(); 3026 if (ariaRole != UnknownRole) 3027 return ariaRole; 3028 3029 RenderBoxModelObject* cssBox = renderBoxModelObject(); 3030 3031 if (node && node->isLink()) { 3032 if (cssBox && cssBox->isImage()) 3033 return ImageMapRole; 3034 return WebCoreLinkRole; 3035 } 3036 if (cssBox && cssBox->isListItem()) 3037 return ListItemRole; 3038 if (m_renderer->isListMarker()) 3039 return ListMarkerRole; 3040 if (node && node->hasTagName(buttonTag)) 3041 return ButtonRole; 3042 if (m_renderer->isText()) 3043 return StaticTextRole; 3044 if (cssBox && cssBox->isImage()) { 3045 if (node && node->hasTagName(inputTag)) 3046 return ButtonRole; 3047 return ImageRole; 3048 } 3049 if (node && node->hasTagName(canvasTag)) 3050 return ImageRole; 3051 3052 if (cssBox && cssBox->isRenderView()) 3053 return WebAreaRole; 3054 3055 if (cssBox && cssBox->isTextField()) 3056 return TextFieldRole; 3057 3058 if (cssBox && cssBox->isTextArea()) 3059 return TextAreaRole; 3060 3061 if (node && node->hasTagName(inputTag)) { 3062 HTMLInputElement* input = static_cast<HTMLInputElement*>(node); 3063 if (input->isCheckbox()) 3064 return CheckBoxRole; 3065 if (input->isRadioButton()) 3066 return RadioButtonRole; 3067 if (input->isTextButton()) 3068 return ButtonRole; 3069 } 3070 3071 if (node && node->hasTagName(buttonTag)) 3072 return ButtonRole; 3073 3074 if (isFileUploadButton()) 3075 return ButtonRole; 3076 3077 if (cssBox && cssBox->isMenuList()) 3078 return PopUpButtonRole; 3079 3080 if (headingLevel()) 3081 return HeadingRole; 3082 3083#if ENABLE(MATHML) 3084 if (node && node->hasTagName(MathMLNames::mathTag)) 3085 return DocumentMathRole; 3086#endif 3087 3088 if (node && node->hasTagName(ddTag)) 3089 return DefinitionListDefinitionRole; 3090 3091 if (node && node->hasTagName(dtTag)) 3092 return DefinitionListTermRole; 3093 3094 if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) 3095 return AnnotationRole; 3096 3097#if PLATFORM(GTK) 3098 // Gtk ATs expect all tables, data and layout, to be exposed as tables. 3099 if (node && (node->hasTagName(tdTag) || node->hasTagName(thTag))) 3100 return CellRole; 3101 3102 if (node && node->hasTagName(trTag)) 3103 return RowRole; 3104 3105 if (node && node->hasTagName(tableTag)) 3106 return TableRole; 3107#endif 3108 3109 // Table sections should be ignored. 3110 if (m_renderer->isTableSection()) 3111 return IgnoredRole; 3112 3113#if PLATFORM(GTK) 3114 if (m_renderer->isHR()) 3115 return SplitterRole; 3116 3117 if (node && node->hasTagName(pTag)) 3118 return ParagraphRole; 3119 3120 if (node && node->hasTagName(labelTag)) 3121 return LabelRole; 3122 3123 if (node && node->hasTagName(divTag)) 3124 return DivRole; 3125 3126 if (node && node->hasTagName(formTag)) 3127 return FormRole; 3128#else 3129 if (node && node->hasTagName(labelTag)) 3130 return GroupRole; 3131#endif 3132 3133 if (m_renderer->isBlockFlow()) 3134 return GroupRole; 3135 3136 // If the element does not have role, but it has ARIA attributes, accessibility should fallback to exposing it as a group. 3137 if (supportsARIAAttributes()) 3138 return GroupRole; 3139 3140 return UnknownRole; 3141} 3142 3143AccessibilityOrientation AccessibilityRenderObject::orientation() const 3144{ 3145 const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); 3146 if (equalIgnoringCase(ariaOrientation, "horizontal")) 3147 return AccessibilityOrientationHorizontal; 3148 if (equalIgnoringCase(ariaOrientation, "vertical")) 3149 return AccessibilityOrientationVertical; 3150 3151 return AccessibilityObject::orientation(); 3152} 3153 3154bool AccessibilityRenderObject::inheritsPresentationalRole() const 3155{ 3156 // ARIA spec says that when a parent object is presentational, and it has required child elements, 3157 // those child elements are also presentational. For example, <li> becomes presentational from <ul>. 3158 // http://www.w3.org/WAI/PF/aria/complete#presentation 3159 DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, listItemParents, ()); 3160 3161 HashSet<QualifiedName>* possibleParentTagNames = 0; 3162 switch (roleValue()) { 3163 case ListItemRole: 3164 case ListMarkerRole: 3165 if (listItemParents.isEmpty()) { 3166 listItemParents.add(ulTag); 3167 listItemParents.add(olTag); 3168 listItemParents.add(dlTag); 3169 } 3170 possibleParentTagNames = &listItemParents; 3171 break; 3172 default: 3173 break; 3174 } 3175 3176 // Not all elements need to check for this, only ones that are required children. 3177 if (!possibleParentTagNames) 3178 return false; 3179 3180 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { 3181 if (!parent->isAccessibilityRenderObject()) 3182 continue; 3183 3184 Node* elementNode = static_cast<AccessibilityRenderObject*>(parent)->node(); 3185 if (!elementNode || !elementNode->isElementNode()) 3186 continue; 3187 3188 // If native tag of the parent element matches an acceptable name, then return 3189 // based on its presentational status. 3190 if (possibleParentTagNames->contains(static_cast<Element*>(elementNode)->tagQName())) 3191 return parent->roleValue() == PresentationalRole; 3192 } 3193 3194 return false; 3195} 3196 3197bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const 3198{ 3199 // Walk the parent chain looking for a parent that has presentational children 3200 AccessibilityObject* parent; 3201 for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject()) 3202 { } 3203 3204 return parent; 3205} 3206 3207bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const 3208{ 3209 switch (m_ariaRole) { 3210 case ButtonRole: 3211 case SliderRole: 3212 case ImageRole: 3213 case ProgressIndicatorRole: 3214 // case SeparatorRole: 3215 return true; 3216 default: 3217 return false; 3218 } 3219} 3220 3221bool AccessibilityRenderObject::canSetFocusAttribute() const 3222{ 3223 ASSERT(m_renderer); 3224 Node* node = m_renderer->node(); 3225 3226 // NOTE: It would be more accurate to ask the document whether setFocusedNode() would 3227 // do anything. For example, setFocusedNode() will do nothing if the current focused 3228 // node will not relinquish the focus. 3229 if (!node || !node->isElementNode()) 3230 return false; 3231 3232 if (!static_cast<Element*>(node)->isEnabledFormControl()) 3233 return false; 3234 3235 switch (roleValue()) { 3236 case WebCoreLinkRole: 3237 case ImageMapLinkRole: 3238 case TextFieldRole: 3239 case TextAreaRole: 3240 case ButtonRole: 3241 case PopUpButtonRole: 3242 case CheckBoxRole: 3243 case RadioButtonRole: 3244 case SliderRole: 3245 return true; 3246 default: 3247 return node->supportsFocus(); 3248 } 3249} 3250 3251bool AccessibilityRenderObject::canSetExpandedAttribute() const 3252{ 3253 // An object can be expanded if it aria-expanded is true or false. 3254 const AtomicString& ariaExpanded = getAttribute(aria_expandedAttr); 3255 return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false"); 3256} 3257 3258bool AccessibilityRenderObject::canSetValueAttribute() const 3259{ 3260 if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true")) 3261 return false; 3262 3263 // Any node could be contenteditable, so isReadOnly should be relied upon 3264 // for this information for all elements. 3265 return isProgressIndicator() || isSlider() || !isReadOnly(); 3266} 3267 3268bool AccessibilityRenderObject::canSetTextRangeAttributes() const 3269{ 3270 return isTextControl(); 3271} 3272 3273void AccessibilityRenderObject::contentChanged() 3274{ 3275 // If this element supports ARIA live regions, then notify the AT of changes. 3276 AXObjectCache* cache = axObjectCache(); 3277 for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) { 3278 AccessibilityObject* parent = cache->get(renderParent); 3279 if (!parent) 3280 continue; 3281 3282 // If we find a parent that has ARIA live region on, send the notification and stop processing. 3283 // The spec does not talk about nested live regions. 3284 if (parent->supportsARIALiveRegion()) { 3285 axObjectCache()->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true); 3286 break; 3287 } 3288 } 3289} 3290 3291void AccessibilityRenderObject::childrenChanged() 3292{ 3293 // This method is meant as a quick way of marking a portion of the accessibility tree dirty. 3294 if (!m_renderer) 3295 return; 3296 3297 bool sentChildrenChanged = false; 3298 3299 // Go up the accessibility parent chain, but only if the element already exists. This method is 3300 // called during render layouts, minimal work should be done. 3301 // If AX elements are created now, they could interrogate the render tree while it's in a funky state. 3302 // At the same time, process ARIA live region changes. 3303 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) { 3304 if (!parent->isAccessibilityRenderObject()) 3305 continue; 3306 3307 AccessibilityRenderObject* axParent = static_cast<AccessibilityRenderObject*>(parent); 3308 3309 // Send the children changed notification on the first accessibility render object ancestor. 3310 if (!sentChildrenChanged) { 3311 axObjectCache()->postNotification(axParent->renderer(), AXObjectCache::AXChildrenChanged, true); 3312 sentChildrenChanged = true; 3313 } 3314 3315 // Only do work if the children haven't been marked dirty. This has the effect of blocking 3316 // future live region change notifications until the AX tree has been accessed again. This 3317 // is a good performance win for all parties. 3318 if (!axParent->needsToUpdateChildren()) { 3319 axParent->setNeedsToUpdateChildren(); 3320 3321 // If this element supports ARIA live regions, then notify the AT of changes. 3322 if (axParent->supportsARIALiveRegion()) 3323 axObjectCache()->postNotification(axParent->renderer(), AXObjectCache::AXLiveRegionChanged, true); 3324 } 3325 } 3326} 3327 3328bool AccessibilityRenderObject::canHaveChildren() const 3329{ 3330 if (!m_renderer) 3331 return false; 3332 3333 // Elements that should not have children 3334 switch (roleValue()) { 3335 case ImageRole: 3336 case ButtonRole: 3337 case PopUpButtonRole: 3338 case CheckBoxRole: 3339 case RadioButtonRole: 3340 case TabRole: 3341 case StaticTextRole: 3342 case ListBoxOptionRole: 3343 case ScrollBarRole: 3344 return false; 3345 default: 3346 return true; 3347 } 3348} 3349 3350void AccessibilityRenderObject::clearChildren() 3351{ 3352 AccessibilityObject::clearChildren(); 3353 m_childrenDirty = false; 3354} 3355 3356void AccessibilityRenderObject::updateChildrenIfNecessary() 3357{ 3358 if (needsToUpdateChildren()) 3359 clearChildren(); 3360 3361 AccessibilityObject::updateChildrenIfNecessary(); 3362} 3363 3364const AccessibilityObject::AccessibilityChildrenVector& AccessibilityRenderObject::children() 3365{ 3366 updateChildrenIfNecessary(); 3367 3368 return m_children; 3369} 3370 3371void AccessibilityRenderObject::addChildren() 3372{ 3373 // If the need to add more children in addition to existing children arises, 3374 // childrenChanged should have been called, leaving the object with no children. 3375 ASSERT(!m_haveChildren); 3376 3377 // nothing to add if there is no RenderObject 3378 if (!m_renderer) 3379 return; 3380 3381 m_haveChildren = true; 3382 3383 if (!canHaveChildren()) 3384 return; 3385 3386 // add all unignored acc children 3387 for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling()) { 3388 if (obj->accessibilityIsIgnored()) { 3389 obj->updateChildrenIfNecessary(); 3390 AccessibilityChildrenVector children = obj->children(); 3391 unsigned length = children.size(); 3392 for (unsigned i = 0; i < length; ++i) 3393 m_children.append(children[i]); 3394 } else { 3395 ASSERT(obj->parentObject() == this); 3396 m_children.append(obj); 3397 } 3398 } 3399 3400 // FrameView's need to be inserted into the AX hierarchy when encountered. 3401 if (isAttachment()) { 3402 Widget* widget = widgetForAttachmentView(); 3403 if (widget && widget->isFrameView()) 3404 m_children.append(axObjectCache()->getOrCreate(widget)); 3405 } 3406 3407 // for a RenderImage, add the <area> elements as individual accessibility objects 3408 RenderBoxModelObject* cssBox = renderBoxModelObject(); 3409 if (cssBox && cssBox->isRenderImage()) { 3410 HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); 3411 if (map) { 3412 for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) { 3413 3414 // add an <area> element for this child if it has a link 3415 if (current->hasTagName(areaTag) && current->isLink()) { 3416 AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole)); 3417 areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(current)); 3418 areaObject->setHTMLMapElement(map); 3419 areaObject->setParent(this); 3420 3421 m_children.append(areaObject); 3422 } 3423 } 3424 } 3425 } 3426} 3427 3428const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const 3429{ 3430 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive")); 3431 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite")); 3432 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off")); 3433 3434 const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); 3435 // These roles have implicit live region status. 3436 if (liveRegionStatus.isEmpty()) { 3437 switch (roleValue()) { 3438 case ApplicationAlertDialogRole: 3439 case ApplicationAlertRole: 3440 return liveRegionStatusAssertive; 3441 case ApplicationLogRole: 3442 case ApplicationStatusRole: 3443 return liveRegionStatusPolite; 3444 case ApplicationTimerRole: 3445 case ApplicationMarqueeRole: 3446 return liveRegionStatusOff; 3447 default: 3448 break; 3449 } 3450 } 3451 3452 return liveRegionStatus; 3453} 3454 3455const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const 3456{ 3457 DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text")); 3458 const AtomicString& relevant = getAttribute(aria_relevantAttr); 3459 3460 // Default aria-relevant = "additions text". 3461 if (relevant.isEmpty()) 3462 return defaultLiveRegionRelevant; 3463 3464 return relevant; 3465} 3466 3467bool AccessibilityRenderObject::ariaLiveRegionAtomic() const 3468{ 3469 return elementAttributeValue(aria_atomicAttr); 3470} 3471 3472bool AccessibilityRenderObject::ariaLiveRegionBusy() const 3473{ 3474 return elementAttributeValue(aria_busyAttr); 3475} 3476 3477void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) 3478{ 3479 // Get all the rows. 3480 AccessibilityChildrenVector allRows; 3481 ariaTreeRows(allRows); 3482 3483 // Determine which rows are selected. 3484 bool isMulti = isMultiSelectable(); 3485 3486 // Prefer active descendant over aria-selected. 3487 AccessibilityObject* activeDesc = activeDescendant(); 3488 if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) { 3489 result.append(activeDesc); 3490 if (!isMulti) 3491 return; 3492 } 3493 3494 unsigned count = allRows.size(); 3495 for (unsigned k = 0; k < count; ++k) { 3496 if (allRows[k]->isSelected()) { 3497 result.append(allRows[k]); 3498 if (!isMulti) 3499 break; 3500 } 3501 } 3502} 3503 3504void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result) 3505{ 3506 bool isMulti = isMultiSelectable(); 3507 3508 AccessibilityChildrenVector childObjects = children(); 3509 unsigned childrenSize = childObjects.size(); 3510 for (unsigned k = 0; k < childrenSize; ++k) { 3511 // Every child should have aria-role option, and if so, check for selected attribute/state. 3512 AccessibilityObject* child = childObjects[k].get(); 3513 if (child->isSelected() && child->ariaRoleAttribute() == ListBoxOptionRole) { 3514 result.append(child); 3515 if (!isMulti) 3516 return; 3517 } 3518 } 3519} 3520 3521void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result) 3522{ 3523 ASSERT(result.isEmpty()); 3524 3525 // only listboxes should be asked for their selected children. 3526 AccessibilityRole role = roleValue(); 3527 if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes 3528 ariaListboxSelectedChildren(result); 3529 else if (role == TreeRole || role == TreeGridRole || role == TableRole) 3530 ariaSelectedRows(result); 3531} 3532 3533void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) 3534{ 3535 if (!hasChildren()) 3536 addChildren(); 3537 3538 unsigned length = m_children.size(); 3539 for (unsigned i = 0; i < length; i++) { 3540 if (!m_children[i]->isOffScreen()) 3541 result.append(m_children[i]); 3542 } 3543} 3544 3545void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result) 3546{ 3547 ASSERT(result.isEmpty()); 3548 3549 // only listboxes are asked for their visible children. 3550 if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes 3551 ASSERT_NOT_REACHED(); 3552 return; 3553 } 3554 return ariaListboxVisibleChildren(result); 3555} 3556 3557void AccessibilityRenderObject::tabChildren(AccessibilityChildrenVector& result) 3558{ 3559 ASSERT(roleValue() == TabListRole); 3560 3561 unsigned length = m_children.size(); 3562 for (unsigned i = 0; i < length; ++i) { 3563 if (m_children[i]->isTabItem()) 3564 result.append(m_children[i]); 3565 } 3566} 3567 3568const String& AccessibilityRenderObject::actionVerb() const 3569{ 3570 // FIXME: Need to add verbs for select elements. 3571 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); 3572 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); 3573 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); 3574 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); 3575 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); 3576 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); 3577 DEFINE_STATIC_LOCAL(const String, noAction, ()); 3578 3579 switch (roleValue()) { 3580 case ButtonRole: 3581 return buttonAction; 3582 case TextFieldRole: 3583 case TextAreaRole: 3584 return textFieldAction; 3585 case RadioButtonRole: 3586 return radioButtonAction; 3587 case CheckBoxRole: 3588 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; 3589 case LinkRole: 3590 case WebCoreLinkRole: 3591 return linkAction; 3592 default: 3593 return noAction; 3594 } 3595} 3596 3597void AccessibilityRenderObject::setAccessibleName(String& name) 3598{ 3599 // Setting the accessible name can store the value in the DOM 3600 if (!m_renderer) 3601 return; 3602 3603 Node* domNode = 0; 3604 // For web areas, set the aria-label on the HTML element. 3605 if (isWebArea()) 3606 domNode = m_renderer->document()->documentElement(); 3607 else 3608 domNode = m_renderer->node(); 3609 3610 if (domNode && domNode->isElementNode()) 3611 static_cast<Element*>(domNode)->setAttribute(aria_labelAttr, name); 3612} 3613 3614void AccessibilityRenderObject::updateBackingStore() 3615{ 3616 if (!m_renderer) 3617 return; 3618 3619 // Updating layout may delete m_renderer and this object. 3620 m_renderer->document()->updateLayoutIgnorePendingStylesheets(); 3621} 3622 3623static bool isLinkable(const AccessibilityRenderObject& object) 3624{ 3625 if (!object.renderer()) 3626 return false; 3627 3628 // See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the elements 3629 // Mozilla considers linkable. 3630 return object.isLink() || object.isImage() || object.renderer()->isText(); 3631} 3632 3633String AccessibilityRenderObject::stringValueForMSAA() const 3634{ 3635 if (isLinkable(*this)) { 3636 Element* anchor = anchorElement(); 3637 if (anchor && anchor->hasTagName(aTag)) 3638 return static_cast<HTMLAnchorElement*>(anchor)->href(); 3639 } 3640 3641 return stringValue(); 3642} 3643 3644bool AccessibilityRenderObject::isLinked() const 3645{ 3646 if (!isLinkable(*this)) 3647 return false; 3648 3649 Element* anchor = anchorElement(); 3650 if (!anchor || !anchor->hasTagName(aTag)) 3651 return false; 3652 3653 return !static_cast<HTMLAnchorElement*>(anchor)->href().isEmpty(); 3654} 3655 3656String AccessibilityRenderObject::nameForMSAA() const 3657{ 3658 if (m_renderer && m_renderer->isText()) 3659 return textUnderElement(); 3660 3661 return title(); 3662} 3663 3664static bool shouldReturnTagNameAsRoleForMSAA(const Element& element) 3665{ 3666 // See "document structure", 3667 // https://wiki.mozilla.org/Accessibility/AT-Windows-API 3668 // FIXME: Add the other tag names that should be returned as the role. 3669 return element.hasTagName(h1Tag) || element.hasTagName(h2Tag) 3670 || element.hasTagName(h3Tag) || element.hasTagName(h4Tag) 3671 || element.hasTagName(h5Tag) || element.hasTagName(h6Tag); 3672} 3673 3674String AccessibilityRenderObject::stringRoleForMSAA() const 3675{ 3676 if (!m_renderer) 3677 return String(); 3678 3679 Node* node = m_renderer->node(); 3680 if (!node || !node->isElementNode()) 3681 return String(); 3682 3683 Element* element = static_cast<Element*>(node); 3684 if (!shouldReturnTagNameAsRoleForMSAA(*element)) 3685 return String(); 3686 3687 return element->tagName(); 3688} 3689 3690String AccessibilityRenderObject::positionalDescriptionForMSAA() const 3691{ 3692 // See "positional descriptions", 3693 // https://wiki.mozilla.org/Accessibility/AT-Windows-API 3694 if (isHeading()) 3695 return "L" + String::number(headingLevel()); 3696 3697 // FIXME: Add positional descriptions for other elements. 3698 return String(); 3699} 3700 3701String AccessibilityRenderObject::descriptionForMSAA() const 3702{ 3703 String description = positionalDescriptionForMSAA(); 3704 if (!description.isEmpty()) 3705 return description; 3706 3707 description = accessibilityDescription(); 3708 if (!description.isEmpty()) { 3709 // From the Mozilla MSAA implementation: 3710 // "Signal to screen readers that this description is speakable and is not 3711 // a formatted positional information description. Don't localize the 3712 // 'Description: ' part of this string, it will be parsed out by assistive 3713 // technologies." 3714 return "Description: " + description; 3715 } 3716 3717 return String(); 3718} 3719 3720static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer) 3721{ 3722 if (!renderer) 3723 return UnknownRole; 3724 3725 if (renderer->isText()) 3726 return EditableTextRole; 3727 3728 if (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListItem()) 3729 return ListItemRole; 3730 3731 return UnknownRole; 3732} 3733 3734AccessibilityRole AccessibilityRenderObject::roleValueForMSAA() const 3735{ 3736 if (m_roleForMSAA != UnknownRole) 3737 return m_roleForMSAA; 3738 3739 m_roleForMSAA = msaaRoleForRenderer(m_renderer); 3740 3741 if (m_roleForMSAA == UnknownRole) 3742 m_roleForMSAA = roleValue(); 3743 3744 return m_roleForMSAA; 3745} 3746 3747} // namespace WebCore 3748