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 59using namespace std; 60 61namespace WebCore { 62 63using namespace HTMLNames; 64 65AccessibilityObject::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 75AccessibilityObject::~AccessibilityObject() 76{ 77 ASSERT(isDetached()); 78} 79 80void AccessibilityObject::detach() 81{ 82#if HAVE(ACCESSIBILITY) 83 setWrapper(0); 84#endif 85} 86 87AccessibilityObject* AccessibilityObject::parentObjectUnignored() const 88{ 89 AccessibilityObject* parent; 90 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { 91 } 92 93 return parent; 94} 95 96AccessibilityObject* 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 125bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) 126{ 127 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; 128} 129 130bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) 131{ 132 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole 133 || ariaRole == ComboBoxRole || ariaRole == SliderRole; 134} 135 136IntPoint AccessibilityObject::clickPoint() const 137{ 138 IntRect rect = elementRect(); 139 return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); 140} 141 142bool 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 153String 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 172VisiblePositionRange 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 200VisiblePositionRange 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 207VisiblePositionRange 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 214static 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 243VisiblePositionRange 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 272VisiblePositionRange 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 305VisiblePositionRange 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 314VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const 315{ 316 VisiblePosition startPosition = startOfParagraph(visiblePos); 317 VisiblePosition endPosition = endOfParagraph(startPosition); 318 return VisiblePositionRange(startPosition, endPosition); 319} 320 321static 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 344static 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 367VisiblePositionRange 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 376VisiblePositionRange 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 388static 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. 404static 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. 415String 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 438String 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 469int 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 496VisiblePosition 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 509VisiblePosition 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 522VisiblePosition 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 544VisiblePosition 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 569VisiblePosition 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 594VisiblePosition 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 618VisiblePosition 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 631VisiblePosition 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 644AccessibilityObject* 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 656int 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 679PlainTextRange 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? 695PlainTextRange 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. 706PlainTextRange 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. 714unsigned AccessibilityObject::doAXLineForIndex(unsigned index) 715{ 716 return lineForPosition(visiblePositionForIndex(index, false)); 717} 718 719Document* AccessibilityObject::document() const 720{ 721 FrameView* frameView = documentFrameView(); 722 if (!frameView) 723 return 0; 724 725 return frameView->frame()->document(); 726} 727 728FrameView* 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 740void AccessibilityObject::updateChildrenIfNecessary() 741{ 742 if (!hasChildren()) 743 addChildren(); 744} 745 746void AccessibilityObject::clearChildren() 747{ 748 m_children.clear(); 749 m_haveChildren = false; 750} 751 752AccessibilityObject* 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 770void 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 786void 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 801void 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 817const 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 852bool AccessibilityObject::ariaIsMultiline() const 853{ 854 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); 855} 856 857const 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 871const 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; 885AccessibilityOrientation 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 897typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; 898 899struct RoleEntry { 900 String ariaRole; 901 AccessibilityRole webcoreRole; 902}; 903 904static 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 977AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) 978{ 979 ASSERT(!value.isEmpty()); 980 static const ARIARoleMap* roleMap = createARIARoleMap(); 981 return roleMap->get(value); 982} 983 984const AtomicString& AccessibilityObject::placeholderValue() const 985{ 986 const AtomicString& placeholder = getAttribute(placeholderAttr); 987 if (!placeholder.isEmpty()) 988 return placeholder; 989 990 return nullAtom; 991} 992 993bool 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 1006bool AccessibilityObject::supportsARIAAttributes() const 1007{ 1008 return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns(); 1009} 1010 1011bool AccessibilityObject::supportsARIALiveRegion() const 1012{ 1013 const AtomicString& liveRegion = ariaLiveRegionStatus(); 1014 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); 1015} 1016 1017AccessibilityObject* 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 1030AXObjectCache* AccessibilityObject::axObjectCache() const 1031{ 1032 Document* doc = document(); 1033 if (doc) 1034 return doc->axObjectCache(); 1035 return 0; 1036} 1037 1038AccessibilityObject* 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 1051AccessibilitySortDirection 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 1062bool AccessibilityObject::supportsARIAExpanded() const 1063{ 1064 return !getAttribute(aria_expandedAttr).isEmpty(); 1065} 1066 1067bool AccessibilityObject::isExpanded() const 1068{ 1069 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) 1070 return true; 1071 1072 return false; 1073} 1074 1075AccessibilityButtonState 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