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