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