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
AccessibilityObject()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
~AccessibilityObject()73 AccessibilityObject::~AccessibilityObject()
74 {
75 ASSERT(isDetached());
76 }
77
detach()78 void AccessibilityObject::detach()
79 {
80 #if HAVE(ACCESSIBILITY)
81 setWrapper(0);
82 #endif
83 }
84
parentObjectUnignored() const85 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
firstAccessibleObjectFromNode(const Node * node)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
isARIAInput(AccessibilityRole ariaRole)123 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
124 {
125 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
126 }
127
isARIAControl(AccessibilityRole ariaRole)128 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
129 {
130 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
131 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
132 }
133
clickPoint() const134 IntPoint AccessibilityObject::clickPoint() const
135 {
136 IntRect rect = elementRect();
137 return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
138 }
139
press() const140 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
language() const151 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
visiblePositionRangeForUnorderedPositions(const VisiblePosition & visiblePos1,const VisiblePosition & visiblePos2) const166 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
positionOfLeftWord(const VisiblePosition & visiblePos) const194 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
positionOfRightWord(const VisiblePosition & visiblePos) const201 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
updateAXLineStartForVisiblePosition(const VisiblePosition & visiblePosition)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
leftLineVisiblePositionRange(const VisiblePosition & visiblePos) const237 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
rightLineVisiblePositionRange(const VisiblePosition & visiblePos) const266 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
sentenceForPosition(const VisiblePosition & visiblePos) const299 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
paragraphForPosition(const VisiblePosition & visiblePos) const308 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
startOfStyleRange(const VisiblePosition visiblePos)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
endOfStyleRange(const VisiblePosition & visiblePos)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
styleRangeForPosition(const VisiblePosition & visiblePos) const361 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
visiblePositionRangeForRange(const PlainTextRange & range) const370 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
replacedNodeNeedsCharacter(Node * replacedNode)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.
renderListItemContainerForNode(Node * node) const397 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.
listMarkerTextForNodeAndPosition(Node * node,const VisiblePosition & visiblePositionStart) const411 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
stringForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const439 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
lengthForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const470 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
nextWordEnd(const VisiblePosition & visiblePos) const497 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
previousWordStart(const VisiblePosition & visiblePos) const510 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
nextLineEndPosition(const VisiblePosition & visiblePos) const523 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
previousLineStartPosition(const VisiblePosition & visiblePos) const545 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
nextSentenceEndPosition(const VisiblePosition & visiblePos) const570 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
previousSentenceStartPosition(const VisiblePosition & visiblePos) const595 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
nextParagraphEndPosition(const VisiblePosition & visiblePos) const619 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
previousParagraphStartPosition(const VisiblePosition & visiblePos) const632 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
accessibilityObjectForPosition(const VisiblePosition & visiblePos) const645 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
lineForPosition(const VisiblePosition & visiblePos) const657 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
plainTextRangeForVisiblePositionRange(const VisiblePositionRange & positionRange) const680 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?
doAXRangeForPosition(const IntPoint & point) const696 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.
doAXStyleRangeForIndex(unsigned index) const707 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.
doAXLineForIndex(unsigned index)715 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
716 {
717 return lineForPosition(visiblePositionForIndex(index, false));
718 }
719
documentFrameView() const720 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
clearChildren()732 void AccessibilityObject::clearChildren()
733 {
734 m_haveChildren = false;
735 m_children.clear();
736 }
737
anchorElementForNode(Node * node)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
ariaTreeRows(AccessibilityChildrenVector & result)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
ariaTreeItemContent(AccessibilityChildrenVector & result)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
ariaTreeItemDisclosedRows(AccessibilityChildrenVector & result)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
actionVerb() const803 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;
orientation() const839 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
createARIARoleMap()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
ariaRoleToWebCoreRole(const String & value)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
isInsideARIALiveRegion() const939 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
supportsARIALiveRegion() const952 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