1 /* 2 * Copyright (C) 2008, 2009, 2011 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 "AccessibilityObject.h" 31 32 #include "AXObjectCache.h" 33 #include "AccessibilityRenderObject.h" 34 #include "FloatRect.h" 35 #include "FocusController.h" 36 #include "Frame.h" 37 #include "FrameLoader.h" 38 #include "LocalizedStrings.h" 39 #include "NodeList.h" 40 #include "NotImplemented.h" 41 #include "Page.h" 42 #include "RenderImage.h" 43 #include "RenderListItem.h" 44 #include "RenderListMarker.h" 45 #include "RenderMenuList.h" 46 #include "RenderTextControl.h" 47 #include "RenderTheme.h" 48 #include "RenderView.h" 49 #include "RenderWidget.h" 50 #include "SelectionController.h" 51 #include "TextIterator.h" 52 #include "htmlediting.h" 53 #include "visible_units.h" 54 #include <wtf/StdLibExtras.h> 55 #include <wtf/text/StringBuilder.h> 56 #include <wtf/text/StringConcatenate.h> 57 #include <wtf/unicode/CharacterNames.h> 58 59 using namespace std; 60 61 namespace WebCore { 62 63 using namespace HTMLNames; 64 65 AccessibilityObject::AccessibilityObject() 66 : m_id(0) 67 , m_haveChildren(false) 68 , m_role(UnknownRole) 69 #if PLATFORM(GTK) 70 , m_wrapper(0) 71 #endif 72 { 73 } 74 75 AccessibilityObject::~AccessibilityObject() 76 { 77 ASSERT(isDetached()); 78 } 79 80 void AccessibilityObject::detach() 81 { 82 #if HAVE(ACCESSIBILITY) 83 setWrapper(0); 84 #endif 85 } 86 87 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const 88 { 89 AccessibilityObject* parent; 90 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { 91 } 92 93 return parent; 94 } 95 96 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) 97 { 98 ASSERT(AXObjectCache::accessibilityEnabled()); 99 100 if (!node) 101 return 0; 102 103 Document* document = node->document(); 104 if (!document) 105 return 0; 106 107 AXObjectCache* cache = document->axObjectCache(); 108 109 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer()); 110 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { 111 node = node->traverseNextNode(); 112 113 while (node && !node->renderer()) 114 node = node->traverseNextSibling(); 115 116 if (!node) 117 return 0; 118 119 accessibleObject = cache->getOrCreate(node->renderer()); 120 } 121 122 return accessibleObject; 123 } 124 125 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) 126 { 127 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; 128 } 129 130 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) 131 { 132 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole 133 || ariaRole == ComboBoxRole || ariaRole == SliderRole; 134 } 135 136 IntPoint AccessibilityObject::clickPoint() const 137 { 138 IntRect rect = elementRect(); 139 return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); 140 } 141 142 bool AccessibilityObject::press() const 143 { 144 Element* actionElem = actionElement(); 145 if (!actionElem) 146 return false; 147 if (Frame* f = actionElem->document()->frame()) 148 f->loader()->resetMultipleFormSubmissionProtection(); 149 actionElem->accessKeyAction(true); 150 return true; 151 } 152 153 String AccessibilityObject::language() const 154 { 155 const AtomicString& lang = getAttribute(langAttr); 156 if (!lang.isEmpty()) 157 return lang; 158 159 AccessibilityObject* parent = parentObject(); 160 161 // as a last resort, fall back to the content language specified in the meta tag 162 if (!parent) { 163 Document* doc = document(); 164 if (doc) 165 return doc->contentLanguage(); 166 return nullAtom; 167 } 168 169 return parent->language(); 170 } 171 172 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const 173 { 174 if (visiblePos1.isNull() || visiblePos2.isNull()) 175 return VisiblePositionRange(); 176 177 VisiblePosition startPos; 178 VisiblePosition endPos; 179 bool alreadyInOrder; 180 181 // upstream is ordered before downstream for the same position 182 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM) 183 alreadyInOrder = false; 184 185 // use selection order to see if the positions are in order 186 else 187 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst(); 188 189 if (alreadyInOrder) { 190 startPos = visiblePos1; 191 endPos = visiblePos2; 192 } else { 193 startPos = visiblePos2; 194 endPos = visiblePos1; 195 } 196 197 return VisiblePositionRange(startPos, endPos); 198 } 199 200 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const 201 { 202 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary); 203 VisiblePosition endPosition = endOfWord(startPosition); 204 return VisiblePositionRange(startPosition, endPosition); 205 } 206 207 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const 208 { 209 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary); 210 VisiblePosition endPosition = endOfWord(startPosition); 211 return VisiblePositionRange(startPosition, endPosition); 212 } 213 214 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition) 215 { 216 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line. 217 // So let's update the position to include that. 218 VisiblePosition tempPosition; 219 VisiblePosition startPosition = visiblePosition; 220 Position p; 221 RenderObject* renderer; 222 while (true) { 223 tempPosition = startPosition.previous(); 224 if (tempPosition.isNull()) 225 break; 226 p = tempPosition.deepEquivalent(); 227 if (!p.deprecatedNode()) 228 break; 229 renderer = p.deprecatedNode()->renderer(); 230 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset())) 231 break; 232 InlineBox* box; 233 int ignoredCaretOffset; 234 p.getInlineBoxAndOffset(tempPosition.affinity(), box, ignoredCaretOffset); 235 if (box) 236 break; 237 startPosition = tempPosition; 238 } 239 240 return startPosition; 241 } 242 243 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const 244 { 245 if (visiblePos.isNull()) 246 return VisiblePositionRange(); 247 248 // make a caret selection for the position before marker position (to make sure 249 // we move off of a line start) 250 VisiblePosition prevVisiblePos = visiblePos.previous(); 251 if (prevVisiblePos.isNull()) 252 return VisiblePositionRange(); 253 254 VisiblePosition startPosition = startOfLine(prevVisiblePos); 255 256 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should 257 // always be a valid line range. However, startOfLine will return null for position next to a floating object, 258 // since floating object doesn't really belong to any line. 259 // This check will reposition the marker before the floating object, to ensure we get a line start. 260 if (startPosition.isNull()) { 261 while (startPosition.isNull() && prevVisiblePos.isNotNull()) { 262 prevVisiblePos = prevVisiblePos.previous(); 263 startPosition = startOfLine(prevVisiblePos); 264 } 265 } else 266 startPosition = updateAXLineStartForVisiblePosition(startPosition); 267 268 VisiblePosition endPosition = endOfLine(prevVisiblePos); 269 return VisiblePositionRange(startPosition, endPosition); 270 } 271 272 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const 273 { 274 if (visiblePos.isNull()) 275 return VisiblePositionRange(); 276 277 // make sure we move off of a line end 278 VisiblePosition nextVisiblePos = visiblePos.next(); 279 if (nextVisiblePos.isNull()) 280 return VisiblePositionRange(); 281 282 VisiblePosition startPosition = startOfLine(nextVisiblePos); 283 284 // fetch for a valid line start position 285 if (startPosition.isNull()) { 286 startPosition = visiblePos; 287 nextVisiblePos = nextVisiblePos.next(); 288 } else 289 startPosition = updateAXLineStartForVisiblePosition(startPosition); 290 291 VisiblePosition endPosition = endOfLine(nextVisiblePos); 292 293 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position 294 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will 295 // return null for position by a floating object, since floating object doesn't really belong to any line. 296 // This check will reposition the marker after the floating object, to ensure we get a line end. 297 while (endPosition.isNull() && nextVisiblePos.isNotNull()) { 298 nextVisiblePos = nextVisiblePos.next(); 299 endPosition = endOfLine(nextVisiblePos); 300 } 301 302 return VisiblePositionRange(startPosition, endPosition); 303 } 304 305 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const 306 { 307 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) 308 // Related? <rdar://problem/3927736> Text selection broken in 8A336 309 VisiblePosition startPosition = startOfSentence(visiblePos); 310 VisiblePosition endPosition = endOfSentence(startPosition); 311 return VisiblePositionRange(startPosition, endPosition); 312 } 313 314 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const 315 { 316 VisiblePosition startPosition = startOfParagraph(visiblePos); 317 VisiblePosition endPosition = endOfParagraph(startPosition); 318 return VisiblePositionRange(startPosition, endPosition); 319 } 320 321 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos) 322 { 323 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); 324 RenderObject* startRenderer = renderer; 325 RenderStyle* style = renderer->style(); 326 327 // traverse backward by renderer to look for style change 328 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { 329 // skip non-leaf nodes 330 if (r->firstChild()) 331 continue; 332 333 // stop at style change 334 if (r->style() != style) 335 break; 336 337 // remember match 338 startRenderer = r; 339 } 340 341 return firstPositionInOrBeforeNode(startRenderer->node()); 342 } 343 344 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos) 345 { 346 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); 347 RenderObject* endRenderer = renderer; 348 RenderStyle* style = renderer->style(); 349 350 // traverse forward by renderer to look for style change 351 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) { 352 // skip non-leaf nodes 353 if (r->firstChild()) 354 continue; 355 356 // stop at style change 357 if (r->style() != style) 358 break; 359 360 // remember match 361 endRenderer = r; 362 } 363 364 return lastPositionInOrAfterNode(endRenderer->node()); 365 } 366 367 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const 368 { 369 if (visiblePos.isNull()) 370 return VisiblePositionRange(); 371 372 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos)); 373 } 374 375 // NOTE: Consider providing this utility method as AX API 376 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const 377 { 378 unsigned textLength = getLengthForTextRange(); 379 if (range.start + range.length > textLength) 380 return VisiblePositionRange(); 381 382 VisiblePosition startPosition = visiblePositionForIndex(range.start); 383 startPosition.setAffinity(DOWNSTREAM); 384 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length); 385 return VisiblePositionRange(startPosition, endPosition); 386 } 387 388 static bool replacedNodeNeedsCharacter(Node* replacedNode) 389 { 390 // we should always be given a rendered node and a replaced node, but be safe 391 // replaced nodes are either attachments (widgets) or images 392 if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) 393 return false; 394 395 // create an AX object, but skip it if it is not supposed to be seen 396 AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer()); 397 if (object->accessibilityIsIgnored()) 398 return false; 399 400 return true; 401 } 402 403 // Finds a RenderListItem parent give a node. 404 static RenderListItem* renderListItemContainerForNode(Node* node) 405 { 406 for (; node; node = node->parentNode()) { 407 RenderBoxModelObject* renderer = node->renderBoxModelObject(); 408 if (renderer && renderer->isListItem()) 409 return toRenderListItem(renderer); 410 } 411 return 0; 412 } 413 414 // Returns the text associated with a list marker if this node is contained within a list item. 415 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const 416 { 417 // If the range does not contain the start of the line, the list marker text should not be included. 418 if (!isStartOfLine(visiblePositionStart)) 419 return String(); 420 421 RenderListItem* listItem = renderListItemContainerForNode(node); 422 if (!listItem) 423 return String(); 424 425 // If this is in a list item, we need to manually add the text for the list marker 426 // because a RenderListMarker does not have a Node equivalent and thus does not appear 427 // when iterating text. 428 const String& markerText = listItem->markerText(); 429 if (markerText.isEmpty()) 430 return String(); 431 432 // Append text, plus the period that follows the text. 433 // FIXME: Not all list marker styles are followed by a period, but this 434 // sounds much better when there is a synthesized pause because of a period. 435 return makeString(markerText, ". "); 436 } 437 438 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const 439 { 440 if (visiblePositionRange.isNull()) 441 return String(); 442 443 StringBuilder builder; 444 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); 445 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { 446 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) 447 if (it.length()) { 448 // Add a textual representation for list marker text 449 String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start); 450 if (!listMarkerText.isEmpty()) 451 builder.append(listMarkerText); 452 453 builder.append(it.characters(), it.length()); 454 } else { 455 // locate the node and starting offset for this replaced range 456 int exception = 0; 457 Node* node = it.range()->startContainer(exception); 458 ASSERT(node == it.range()->endContainer(exception)); 459 int offset = it.range()->startOffset(exception); 460 461 if (replacedNodeNeedsCharacter(node->childNode(offset))) 462 builder.append(objectReplacementCharacter); 463 } 464 } 465 466 return builder.toString(); 467 } 468 469 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const 470 { 471 // FIXME: Multi-byte support 472 if (visiblePositionRange.isNull()) 473 return -1; 474 475 int length = 0; 476 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); 477 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { 478 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) 479 if (it.length()) 480 length += it.length(); 481 else { 482 // locate the node and starting offset for this replaced range 483 int exception = 0; 484 Node* node = it.range()->startContainer(exception); 485 ASSERT(node == it.range()->endContainer(exception)); 486 int offset = it.range()->startOffset(exception); 487 488 if (replacedNodeNeedsCharacter(node->childNode(offset))) 489 length++; 490 } 491 } 492 493 return length; 494 } 495 496 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const 497 { 498 if (visiblePos.isNull()) 499 return VisiblePosition(); 500 501 // make sure we move off of a word end 502 VisiblePosition nextVisiblePos = visiblePos.next(); 503 if (nextVisiblePos.isNull()) 504 return VisiblePosition(); 505 506 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary); 507 } 508 509 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const 510 { 511 if (visiblePos.isNull()) 512 return VisiblePosition(); 513 514 // make sure we move off of a word start 515 VisiblePosition prevVisiblePos = visiblePos.previous(); 516 if (prevVisiblePos.isNull()) 517 return VisiblePosition(); 518 519 return startOfWord(prevVisiblePos, RightWordIfOnBoundary); 520 } 521 522 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const 523 { 524 if (visiblePos.isNull()) 525 return VisiblePosition(); 526 527 // to make sure we move off of a line end 528 VisiblePosition nextVisiblePos = visiblePos.next(); 529 if (nextVisiblePos.isNull()) 530 return VisiblePosition(); 531 532 VisiblePosition endPosition = endOfLine(nextVisiblePos); 533 534 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position 535 // There are cases like when the position is next to a floating object that'll return null for end of line. This code will avoid returning null. 536 while (endPosition.isNull() && nextVisiblePos.isNotNull()) { 537 nextVisiblePos = nextVisiblePos.next(); 538 endPosition = endOfLine(nextVisiblePos); 539 } 540 541 return endPosition; 542 } 543 544 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const 545 { 546 if (visiblePos.isNull()) 547 return VisiblePosition(); 548 549 // make sure we move off of a line start 550 VisiblePosition prevVisiblePos = visiblePos.previous(); 551 if (prevVisiblePos.isNull()) 552 return VisiblePosition(); 553 554 VisiblePosition startPosition = startOfLine(prevVisiblePos); 555 556 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position 557 // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null. 558 if (startPosition.isNull()) { 559 while (startPosition.isNull() && prevVisiblePos.isNotNull()) { 560 prevVisiblePos = prevVisiblePos.previous(); 561 startPosition = startOfLine(prevVisiblePos); 562 } 563 } else 564 startPosition = updateAXLineStartForVisiblePosition(startPosition); 565 566 return startPosition; 567 } 568 569 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const 570 { 571 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) 572 // Related? <rdar://problem/3927736> Text selection broken in 8A336 573 if (visiblePos.isNull()) 574 return VisiblePosition(); 575 576 // make sure we move off of a sentence end 577 VisiblePosition nextVisiblePos = visiblePos.next(); 578 if (nextVisiblePos.isNull()) 579 return VisiblePosition(); 580 581 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not 582 // see this empty line. Instead, return the end position of the empty line. 583 VisiblePosition endPosition; 584 585 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get()); 586 if (lineString.isEmpty()) 587 endPosition = nextVisiblePos; 588 else 589 endPosition = endOfSentence(nextVisiblePos); 590 591 return endPosition; 592 } 593 594 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const 595 { 596 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) 597 // Related? <rdar://problem/3927736> Text selection broken in 8A336 598 if (visiblePos.isNull()) 599 return VisiblePosition(); 600 601 // make sure we move off of a sentence start 602 VisiblePosition previousVisiblePos = visiblePos.previous(); 603 if (previousVisiblePos.isNull()) 604 return VisiblePosition(); 605 606 // treat empty line as a separate sentence. 607 VisiblePosition startPosition; 608 609 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get()); 610 if (lineString.isEmpty()) 611 startPosition = previousVisiblePos; 612 else 613 startPosition = startOfSentence(previousVisiblePos); 614 615 return startPosition; 616 } 617 618 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const 619 { 620 if (visiblePos.isNull()) 621 return VisiblePosition(); 622 623 // make sure we move off of a paragraph end 624 VisiblePosition nextPos = visiblePos.next(); 625 if (nextPos.isNull()) 626 return VisiblePosition(); 627 628 return endOfParagraph(nextPos); 629 } 630 631 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const 632 { 633 if (visiblePos.isNull()) 634 return VisiblePosition(); 635 636 // make sure we move off of a paragraph start 637 VisiblePosition previousPos = visiblePos.previous(); 638 if (previousPos.isNull()) 639 return VisiblePosition(); 640 641 return startOfParagraph(previousPos); 642 } 643 644 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const 645 { 646 if (visiblePos.isNull()) 647 return 0; 648 649 RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer(); 650 if (!obj) 651 return 0; 652 653 return obj->document()->axObjectCache()->getOrCreate(obj); 654 } 655 656 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const 657 { 658 if (visiblePos.isNull()) 659 return 0; 660 661 unsigned lineCount = 0; 662 VisiblePosition currentVisiblePos = visiblePos; 663 VisiblePosition savedVisiblePos; 664 665 // move up until we get to the top 666 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the 667 // top document. 668 while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) { 669 ++lineCount; 670 savedVisiblePos = currentVisiblePos; 671 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0); 672 currentVisiblePos = prevVisiblePos; 673 } 674 675 return lineCount - 1; 676 } 677 678 // NOTE: Consider providing this utility method as AX API 679 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const 680 { 681 int index1 = index(positionRange.start); 682 int index2 = index(positionRange.end); 683 if (index1 < 0 || index2 < 0 || index1 > index2) 684 return PlainTextRange(); 685 686 return PlainTextRange(index1, index2 - index1); 687 } 688 689 // The composed character range in the text associated with this accessibility object that 690 // is specified by the given screen coordinates. This parameterized attribute returns the 691 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given 692 // screen coordinates. 693 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an 694 // an error in that case. We return textControl->text().length(), 1. Does this matter? 695 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const 696 { 697 int i = index(visiblePositionForPoint(point)); 698 if (i < 0) 699 return PlainTextRange(); 700 701 return PlainTextRange(i, 1); 702 } 703 704 // Given a character index, the range of text associated with this accessibility object 705 // over which the style in effect at that character index applies. 706 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const 707 { 708 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false)); 709 return plainTextRangeForVisiblePositionRange(range); 710 } 711 712 // Given an indexed character, the line number of the text associated with this accessibility 713 // object that contains the character. 714 unsigned AccessibilityObject::doAXLineForIndex(unsigned index) 715 { 716 return lineForPosition(visiblePositionForIndex(index, false)); 717 } 718 719 Document* AccessibilityObject::document() const 720 { 721 FrameView* frameView = documentFrameView(); 722 if (!frameView) 723 return 0; 724 725 return frameView->frame()->document(); 726 } 727 728 FrameView* AccessibilityObject::documentFrameView() const 729 { 730 const AccessibilityObject* object = this; 731 while (object && !object->isAccessibilityRenderObject()) 732 object = object->parentObject(); 733 734 if (!object) 735 return 0; 736 737 return object->documentFrameView(); 738 } 739 740 void AccessibilityObject::updateChildrenIfNecessary() 741 { 742 if (!hasChildren()) 743 addChildren(); 744 } 745 746 void AccessibilityObject::clearChildren() 747 { 748 m_children.clear(); 749 m_haveChildren = false; 750 } 751 752 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) 753 { 754 RenderObject* obj = node->renderer(); 755 if (!obj) 756 return 0; 757 758 RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj); 759 Element* anchor = axObj->anchorElement(); 760 if (!anchor) 761 return 0; 762 763 RenderObject* anchorRenderer = anchor->renderer(); 764 if (!anchorRenderer) 765 return 0; 766 767 return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer); 768 } 769 770 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result) 771 { 772 AccessibilityChildrenVector axChildren = children(); 773 unsigned count = axChildren.size(); 774 for (unsigned k = 0; k < count; ++k) { 775 AccessibilityObject* obj = axChildren[k].get(); 776 777 // Add tree items as the rows. 778 if (obj->roleValue() == TreeItemRole) 779 result.append(obj); 780 781 // Now see if this item also has rows hiding inside of it. 782 obj->ariaTreeRows(result); 783 } 784 } 785 786 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result) 787 { 788 // The ARIA tree item content are the item that are not other tree items or their containing groups. 789 AccessibilityChildrenVector axChildren = children(); 790 unsigned count = axChildren.size(); 791 for (unsigned k = 0; k < count; ++k) { 792 AccessibilityObject* obj = axChildren[k].get(); 793 AccessibilityRole role = obj->roleValue(); 794 if (role == TreeItemRole || role == GroupRole) 795 continue; 796 797 result.append(obj); 798 } 799 } 800 801 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result) 802 { 803 AccessibilityChildrenVector axChildren = children(); 804 unsigned count = axChildren.size(); 805 for (unsigned k = 0; k < count; ++k) { 806 AccessibilityObject* obj = axChildren[k].get(); 807 808 // Add tree items as the rows. 809 if (obj->roleValue() == TreeItemRole) 810 result.append(obj); 811 // If it's not a tree item, then descend into the group to find more tree items. 812 else 813 obj->ariaTreeRows(result); 814 } 815 } 816 817 const String& AccessibilityObject::actionVerb() const 818 { 819 // FIXME: Need to add verbs for select elements. 820 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); 821 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); 822 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); 823 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); 824 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); 825 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); 826 DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb())); 827 DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb())); 828 DEFINE_STATIC_LOCAL(const String, noAction, ()); 829 830 switch (roleValue()) { 831 case ButtonRole: 832 return buttonAction; 833 case TextFieldRole: 834 case TextAreaRole: 835 return textFieldAction; 836 case RadioButtonRole: 837 return radioButtonAction; 838 case CheckBoxRole: 839 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; 840 case LinkRole: 841 case WebCoreLinkRole: 842 return linkAction; 843 case PopUpButtonRole: 844 return menuListAction; 845 case MenuListPopupRole: 846 return menuListPopupAction; 847 default: 848 return noAction; 849 } 850 } 851 852 bool AccessibilityObject::ariaIsMultiline() const 853 { 854 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); 855 } 856 857 const AtomicString& AccessibilityObject::invalidStatus() const 858 { 859 DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false")); 860 861 // aria-invalid can return false (default), grammer, spelling, or true. 862 const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr); 863 864 // If empty or not present, it should return false. 865 if (ariaInvalid.isEmpty()) 866 return invalidStatusFalse; 867 868 return ariaInvalid; 869 } 870 871 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const 872 { 873 Node* elementNode = node(); 874 if (!elementNode) 875 return nullAtom; 876 877 if (!elementNode->isElementNode()) 878 return nullAtom; 879 880 Element* element = static_cast<Element*>(elementNode); 881 return element->fastGetAttribute(attribute); 882 } 883 884 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; 885 AccessibilityOrientation AccessibilityObject::orientation() const 886 { 887 IntRect bounds = elementRect(); 888 if (bounds.size().width() > bounds.size().height()) 889 return AccessibilityOrientationHorizontal; 890 if (bounds.size().height() > bounds.size().width()) 891 return AccessibilityOrientationVertical; 892 893 // A tie goes to horizontal. 894 return AccessibilityOrientationHorizontal; 895 } 896 897 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; 898 899 struct RoleEntry { 900 String ariaRole; 901 AccessibilityRole webcoreRole; 902 }; 903 904 static ARIARoleMap* createARIARoleMap() 905 { 906 const RoleEntry roles[] = { 907 { "alert", ApplicationAlertRole }, 908 { "alertdialog", ApplicationAlertDialogRole }, 909 { "application", LandmarkApplicationRole }, 910 { "article", DocumentArticleRole }, 911 { "banner", LandmarkBannerRole }, 912 { "button", ButtonRole }, 913 { "checkbox", CheckBoxRole }, 914 { "complementary", LandmarkComplementaryRole }, 915 { "contentinfo", LandmarkContentInfoRole }, 916 { "dialog", ApplicationDialogRole }, 917 { "directory", DirectoryRole }, 918 { "grid", TableRole }, 919 { "gridcell", CellRole }, 920 { "columnheader", ColumnHeaderRole }, 921 { "combobox", ComboBoxRole }, 922 { "definition", DefinitionListDefinitionRole }, 923 { "document", DocumentRole }, 924 { "rowheader", RowHeaderRole }, 925 { "group", GroupRole }, 926 { "heading", HeadingRole }, 927 { "img", ImageRole }, 928 { "link", WebCoreLinkRole }, 929 { "list", ListRole }, 930 { "listitem", ListItemRole }, 931 { "listbox", ListBoxRole }, 932 { "log", ApplicationLogRole }, 933 // "option" isn't here because it may map to different roles depending on the parent element's role 934 { "main", LandmarkMainRole }, 935 { "marquee", ApplicationMarqueeRole }, 936 { "math", DocumentMathRole }, 937 { "menu", MenuRole }, 938 { "menubar", MenuBarRole }, 939 // "menuitem" isn't here because it may map to different roles depending on the parent element's role 940 { "menuitemcheckbox", MenuItemRole }, 941 { "menuitemradio", MenuItemRole }, 942 { "note", DocumentNoteRole }, 943 { "navigation", LandmarkNavigationRole }, 944 { "option", ListBoxOptionRole }, 945 { "presentation", PresentationalRole }, 946 { "progressbar", ProgressIndicatorRole }, 947 { "radio", RadioButtonRole }, 948 { "radiogroup", RadioGroupRole }, 949 { "region", DocumentRegionRole }, 950 { "row", RowRole }, 951 { "range", SliderRole }, 952 { "scrollbar", ScrollBarRole }, 953 { "search", LandmarkSearchRole }, 954 { "separator", SplitterRole }, 955 { "slider", SliderRole }, 956 { "spinbutton", ProgressIndicatorRole }, 957 { "status", ApplicationStatusRole }, 958 { "tab", TabRole }, 959 { "tablist", TabListRole }, 960 { "tabpanel", TabPanelRole }, 961 { "text", StaticTextRole }, 962 { "textbox", TextAreaRole }, 963 { "timer", ApplicationTimerRole }, 964 { "toolbar", ToolbarRole }, 965 { "tooltip", UserInterfaceTooltipRole }, 966 { "tree", TreeRole }, 967 { "treegrid", TreeGridRole }, 968 { "treeitem", TreeItemRole } 969 }; 970 ARIARoleMap* roleMap = new ARIARoleMap; 971 972 for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i) 973 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole); 974 return roleMap; 975 } 976 977 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) 978 { 979 ASSERT(!value.isEmpty()); 980 static const ARIARoleMap* roleMap = createARIARoleMap(); 981 return roleMap->get(value); 982 } 983 984 const AtomicString& AccessibilityObject::placeholderValue() const 985 { 986 const AtomicString& placeholder = getAttribute(placeholderAttr); 987 if (!placeholder.isEmpty()) 988 return placeholder; 989 990 return nullAtom; 991 } 992 993 bool AccessibilityObject::isInsideARIALiveRegion() const 994 { 995 if (supportsARIALiveRegion()) 996 return true; 997 998 for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) { 999 if (axParent->supportsARIALiveRegion()) 1000 return true; 1001 } 1002 1003 return false; 1004 } 1005 1006 bool AccessibilityObject::supportsARIAAttributes() const 1007 { 1008 return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns(); 1009 } 1010 1011 bool AccessibilityObject::supportsARIALiveRegion() const 1012 { 1013 const AtomicString& liveRegion = ariaLiveRegionStatus(); 1014 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); 1015 } 1016 1017 AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const 1018 { 1019 // Send the hit test back into the sub-frame if necessary. 1020 if (isAttachment()) { 1021 Widget* widget = widgetForAttachmentView(); 1022 // Normalize the point for the widget's bounds. 1023 if (widget && widget->isFrameView()) 1024 return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); 1025 } 1026 1027 return const_cast<AccessibilityObject*>(this); 1028 } 1029 1030 AXObjectCache* AccessibilityObject::axObjectCache() const 1031 { 1032 Document* doc = document(); 1033 if (doc) 1034 return doc->axObjectCache(); 1035 return 0; 1036 } 1037 1038 AccessibilityObject* AccessibilityObject::focusedUIElement() const 1039 { 1040 Document* doc = document(); 1041 if (!doc) 1042 return 0; 1043 1044 Page* page = doc->page(); 1045 if (!page) 1046 return 0; 1047 1048 return AXObjectCache::focusedUIElementForPage(page); 1049 } 1050 1051 AccessibilitySortDirection AccessibilityObject::sortDirection() const 1052 { 1053 const AtomicString& sortAttribute = getAttribute(aria_sortAttr); 1054 if (equalIgnoringCase(sortAttribute, "ascending")) 1055 return SortDirectionAscending; 1056 if (equalIgnoringCase(sortAttribute, "descending")) 1057 return SortDirectionDescending; 1058 1059 return SortDirectionNone; 1060 } 1061 1062 bool AccessibilityObject::supportsARIAExpanded() const 1063 { 1064 return !getAttribute(aria_expandedAttr).isEmpty(); 1065 } 1066 1067 bool AccessibilityObject::isExpanded() const 1068 { 1069 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) 1070 return true; 1071 1072 return false; 1073 } 1074 1075 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const 1076 { 1077 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle. 1078 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used. 1079 1080 const AtomicString& result = getAttribute(aria_checkedAttr); 1081 if (equalIgnoringCase(result, "true")) 1082 return ButtonStateOn; 1083 if (equalIgnoringCase(result, "mixed")) 1084 return ButtonStateMixed; 1085 1086 return ButtonStateOff; 1087 } 1088 1089 } // namespace WebCore 1090