1 /*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "AccessibilityObject.h"
31
32 #include "AccessibilityRenderObject.h"
33 #include "AXObjectCache.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 "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
56 using namespace std;
57
58 namespace WebCore {
59
60 using namespace HTMLNames;
61
AccessibilityObject()62 AccessibilityObject::AccessibilityObject()
63 : m_id(0)
64 , m_haveChildren(false)
65 , m_role(UnknownRole)
66 #if PLATFORM(GTK)
67 , m_wrapper(0)
68 #endif
69 {
70 }
71
~AccessibilityObject()72 AccessibilityObject::~AccessibilityObject()
73 {
74 ASSERT(isDetached());
75 }
76
detach()77 void AccessibilityObject::detach()
78 {
79 #if HAVE(ACCESSIBILITY)
80 setWrapper(0);
81 #endif
82 }
83
parentObjectUnignored() const84 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
85 {
86 AccessibilityObject* parent;
87 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject())
88 ;
89 return parent;
90 }
91
isARIAInput(AccessibilityRole ariaRole)92 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
93 {
94 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
95 }
96
isARIAControl(AccessibilityRole ariaRole)97 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
98 {
99 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
100 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
101 }
102
clickPoint() const103 IntPoint AccessibilityObject::clickPoint() const
104 {
105 IntRect rect = elementRect();
106 return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
107 }
108
press() const109 bool AccessibilityObject::press() const
110 {
111 Element* actionElem = actionElement();
112 if (!actionElem)
113 return false;
114 if (Frame* f = actionElem->document()->frame())
115 f->loader()->resetMultipleFormSubmissionProtection();
116 actionElem->accessKeyAction(true);
117 return true;
118 }
119
language() const120 String AccessibilityObject::language() const
121 {
122 AccessibilityObject* parent = parentObject();
123
124 // as a last resort, fall back to the content language specified in the meta tag
125 if (!parent) {
126 Document* doc = document();
127 if (doc)
128 return doc->contentLanguage();
129 return String();
130 }
131
132 return parent->language();
133 }
134
visiblePositionRangeForUnorderedPositions(const VisiblePosition & visiblePos1,const VisiblePosition & visiblePos2) const135 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
136 {
137 if (visiblePos1.isNull() || visiblePos2.isNull())
138 return VisiblePositionRange();
139
140 VisiblePosition startPos;
141 VisiblePosition endPos;
142 bool alreadyInOrder;
143
144 // upstream is ordered before downstream for the same position
145 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
146 alreadyInOrder = false;
147
148 // use selection order to see if the positions are in order
149 else
150 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
151
152 if (alreadyInOrder) {
153 startPos = visiblePos1;
154 endPos = visiblePos2;
155 } else {
156 startPos = visiblePos2;
157 endPos = visiblePos1;
158 }
159
160 return VisiblePositionRange(startPos, endPos);
161 }
162
positionOfLeftWord(const VisiblePosition & visiblePos) const163 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
164 {
165 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
166 VisiblePosition endPosition = endOfWord(startPosition);
167 return VisiblePositionRange(startPosition, endPosition);
168 }
169
positionOfRightWord(const VisiblePosition & visiblePos) const170 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
171 {
172 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
173 VisiblePosition endPosition = endOfWord(startPosition);
174 return VisiblePositionRange(startPosition, endPosition);
175 }
176
updateAXLineStartForVisiblePosition(const VisiblePosition & visiblePosition)177 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
178 {
179 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
180 // So let's update the position to include that.
181 VisiblePosition tempPosition;
182 VisiblePosition startPosition = visiblePosition;
183 Position p;
184 RenderObject* renderer;
185 while (true) {
186 tempPosition = startPosition.previous();
187 if (tempPosition.isNull())
188 break;
189 p = tempPosition.deepEquivalent();
190 if (!p.node())
191 break;
192 renderer = p.node()->renderer();
193 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
194 break;
195 InlineBox* box;
196 int ignoredCaretOffset;
197 p.getInlineBoxAndOffset(tempPosition.affinity(), box, ignoredCaretOffset);
198 if (box)
199 break;
200 startPosition = tempPosition;
201 }
202
203 return startPosition;
204 }
205
leftLineVisiblePositionRange(const VisiblePosition & visiblePos) const206 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
207 {
208 if (visiblePos.isNull())
209 return VisiblePositionRange();
210
211 // make a caret selection for the position before marker position (to make sure
212 // we move off of a line start)
213 VisiblePosition prevVisiblePos = visiblePos.previous();
214 if (prevVisiblePos.isNull())
215 return VisiblePositionRange();
216
217 VisiblePosition startPosition = startOfLine(prevVisiblePos);
218
219 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
220 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
221 // since floating object doesn't really belong to any line.
222 // This check will reposition the marker before the floating object, to ensure we get a line start.
223 if (startPosition.isNull()) {
224 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
225 prevVisiblePos = prevVisiblePos.previous();
226 startPosition = startOfLine(prevVisiblePos);
227 }
228 } else
229 startPosition = updateAXLineStartForVisiblePosition(startPosition);
230
231 VisiblePosition endPosition = endOfLine(prevVisiblePos);
232 return VisiblePositionRange(startPosition, endPosition);
233 }
234
rightLineVisiblePositionRange(const VisiblePosition & visiblePos) const235 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
236 {
237 if (visiblePos.isNull())
238 return VisiblePositionRange();
239
240 // make sure we move off of a line end
241 VisiblePosition nextVisiblePos = visiblePos.next();
242 if (nextVisiblePos.isNull())
243 return VisiblePositionRange();
244
245 VisiblePosition startPosition = startOfLine(nextVisiblePos);
246
247 // fetch for a valid line start position
248 if (startPosition.isNull() ) {
249 startPosition = visiblePos;
250 nextVisiblePos = nextVisiblePos.next();
251 } else
252 startPosition = updateAXLineStartForVisiblePosition(startPosition);
253
254 VisiblePosition endPosition = endOfLine(nextVisiblePos);
255
256 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
257 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
258 // return null for position by a floating object, since floating object doesn't really belong to any line.
259 // This check will reposition the marker after the floating object, to ensure we get a line end.
260 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
261 nextVisiblePos = nextVisiblePos.next();
262 endPosition = endOfLine(nextVisiblePos);
263 }
264
265 return VisiblePositionRange(startPosition, endPosition);
266 }
267
sentenceForPosition(const VisiblePosition & visiblePos) const268 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
269 {
270 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
271 // Related? <rdar://problem/3927736> Text selection broken in 8A336
272 VisiblePosition startPosition = startOfSentence(visiblePos);
273 VisiblePosition endPosition = endOfSentence(startPosition);
274 return VisiblePositionRange(startPosition, endPosition);
275 }
276
paragraphForPosition(const VisiblePosition & visiblePos) const277 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
278 {
279 VisiblePosition startPosition = startOfParagraph(visiblePos);
280 VisiblePosition endPosition = endOfParagraph(startPosition);
281 return VisiblePositionRange(startPosition, endPosition);
282 }
283
startOfStyleRange(const VisiblePosition visiblePos)284 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos)
285 {
286 RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
287 RenderObject* startRenderer = renderer;
288 RenderStyle* style = renderer->style();
289
290 // traverse backward by renderer to look for style change
291 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
292 // skip non-leaf nodes
293 if (r->firstChild())
294 continue;
295
296 // stop at style change
297 if (r->style() != style)
298 break;
299
300 // remember match
301 startRenderer = r;
302 }
303
304 return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY);
305 }
306
endOfStyleRange(const VisiblePosition & visiblePos)307 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
308 {
309 RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
310 RenderObject* endRenderer = renderer;
311 RenderStyle* style = renderer->style();
312
313 // traverse forward by renderer to look for style change
314 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
315 // skip non-leaf nodes
316 if (r->firstChild())
317 continue;
318
319 // stop at style change
320 if (r->style() != style)
321 break;
322
323 // remember match
324 endRenderer = r;
325 }
326
327 return lastDeepEditingPositionForNode(endRenderer->node());
328 }
329
styleRangeForPosition(const VisiblePosition & visiblePos) const330 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
331 {
332 if (visiblePos.isNull())
333 return VisiblePositionRange();
334
335 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
336 }
337
338 // NOTE: Consider providing this utility method as AX API
visiblePositionRangeForRange(const PlainTextRange & range) const339 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
340 {
341 if (range.start + range.length > text().length())
342 return VisiblePositionRange();
343
344 VisiblePosition startPosition = visiblePositionForIndex(range.start);
345 startPosition.setAffinity(DOWNSTREAM);
346 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
347 return VisiblePositionRange(startPosition, endPosition);
348 }
349
replacedNodeNeedsCharacter(Node * replacedNode)350 static bool replacedNodeNeedsCharacter(Node* replacedNode)
351 {
352 // we should always be given a rendered node and a replaced node, but be safe
353 // replaced nodes are either attachments (widgets) or images
354 if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) {
355 return false;
356 }
357
358 // create an AX object, but skip it if it is not supposed to be seen
359 AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer());
360 if (object->accessibilityIsIgnored())
361 return false;
362
363 return true;
364 }
365
stringForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const366 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
367 {
368 if (visiblePositionRange.isNull())
369 return String();
370
371 Vector<UChar> resultVector;
372 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
373 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
374 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
375 if (it.length() != 0) {
376 resultVector.append(it.characters(), it.length());
377 } else {
378 // locate the node and starting offset for this replaced range
379 int exception = 0;
380 Node* node = it.range()->startContainer(exception);
381 ASSERT(node == it.range()->endContainer(exception));
382 int offset = it.range()->startOffset(exception);
383
384 if (replacedNodeNeedsCharacter(node->childNode(offset))) {
385 resultVector.append(objectReplacementCharacter);
386 }
387 }
388 }
389
390 return String::adopt(resultVector);
391 }
392
lengthForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const393 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
394 {
395 // FIXME: Multi-byte support
396 if (visiblePositionRange.isNull())
397 return -1;
398
399 int length = 0;
400 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
401 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
402 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
403 if (it.length() != 0) {
404 length += it.length();
405 } else {
406 // locate the node and starting offset for this replaced range
407 int exception = 0;
408 Node* node = it.range()->startContainer(exception);
409 ASSERT(node == it.range()->endContainer(exception));
410 int offset = it.range()->startOffset(exception);
411
412 if (replacedNodeNeedsCharacter(node->childNode(offset)))
413 length++;
414 }
415 }
416
417 return length;
418 }
419
nextWordEnd(const VisiblePosition & visiblePos) const420 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
421 {
422 if (visiblePos.isNull())
423 return VisiblePosition();
424
425 // make sure we move off of a word end
426 VisiblePosition nextVisiblePos = visiblePos.next();
427 if (nextVisiblePos.isNull())
428 return VisiblePosition();
429
430 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
431 }
432
previousWordStart(const VisiblePosition & visiblePos) const433 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
434 {
435 if (visiblePos.isNull())
436 return VisiblePosition();
437
438 // make sure we move off of a word start
439 VisiblePosition prevVisiblePos = visiblePos.previous();
440 if (prevVisiblePos.isNull())
441 return VisiblePosition();
442
443 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
444 }
445
nextLineEndPosition(const VisiblePosition & visiblePos) const446 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
447 {
448 if (visiblePos.isNull())
449 return VisiblePosition();
450
451 // to make sure we move off of a line end
452 VisiblePosition nextVisiblePos = visiblePos.next();
453 if (nextVisiblePos.isNull())
454 return VisiblePosition();
455
456 VisiblePosition endPosition = endOfLine(nextVisiblePos);
457
458 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
459 // 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.
460 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
461 nextVisiblePos = nextVisiblePos.next();
462 endPosition = endOfLine(nextVisiblePos);
463 }
464
465 return endPosition;
466 }
467
previousLineStartPosition(const VisiblePosition & visiblePos) const468 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
469 {
470 if (visiblePos.isNull())
471 return VisiblePosition();
472
473 // make sure we move off of a line start
474 VisiblePosition prevVisiblePos = visiblePos.previous();
475 if (prevVisiblePos.isNull())
476 return VisiblePosition();
477
478 VisiblePosition startPosition = startOfLine(prevVisiblePos);
479
480 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
481 // 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.
482 if (startPosition.isNull()) {
483 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
484 prevVisiblePos = prevVisiblePos.previous();
485 startPosition = startOfLine(prevVisiblePos);
486 }
487 } else
488 startPosition = updateAXLineStartForVisiblePosition(startPosition);
489
490 return startPosition;
491 }
492
nextSentenceEndPosition(const VisiblePosition & visiblePos) const493 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
494 {
495 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
496 // Related? <rdar://problem/3927736> Text selection broken in 8A336
497 if (visiblePos.isNull())
498 return VisiblePosition();
499
500 // make sure we move off of a sentence end
501 VisiblePosition nextVisiblePos = visiblePos.next();
502 if (nextVisiblePos.isNull())
503 return VisiblePosition();
504
505 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
506 // see this empty line. Instead, return the end position of the empty line.
507 VisiblePosition endPosition;
508
509 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
510 if (lineString.isEmpty())
511 endPosition = nextVisiblePos;
512 else
513 endPosition = endOfSentence(nextVisiblePos);
514
515 return endPosition;
516 }
517
previousSentenceStartPosition(const VisiblePosition & visiblePos) const518 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
519 {
520 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
521 // Related? <rdar://problem/3927736> Text selection broken in 8A336
522 if (visiblePos.isNull())
523 return VisiblePosition();
524
525 // make sure we move off of a sentence start
526 VisiblePosition previousVisiblePos = visiblePos.previous();
527 if (previousVisiblePos.isNull())
528 return VisiblePosition();
529
530 // treat empty line as a separate sentence.
531 VisiblePosition startPosition;
532
533 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
534 if (lineString.isEmpty())
535 startPosition = previousVisiblePos;
536 else
537 startPosition = startOfSentence(previousVisiblePos);
538
539 return startPosition;
540 }
541
nextParagraphEndPosition(const VisiblePosition & visiblePos) const542 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
543 {
544 if (visiblePos.isNull())
545 return VisiblePosition();
546
547 // make sure we move off of a paragraph end
548 VisiblePosition nextPos = visiblePos.next();
549 if (nextPos.isNull())
550 return VisiblePosition();
551
552 return endOfParagraph(nextPos);
553 }
554
previousParagraphStartPosition(const VisiblePosition & visiblePos) const555 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
556 {
557 if (visiblePos.isNull())
558 return VisiblePosition();
559
560 // make sure we move off of a paragraph start
561 VisiblePosition previousPos = visiblePos.previous();
562 if (previousPos.isNull())
563 return VisiblePosition();
564
565 return startOfParagraph(previousPos);
566 }
567
accessibilityObjectForPosition(const VisiblePosition & visiblePos) const568 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
569 {
570 if (visiblePos.isNull())
571 return 0;
572
573 RenderObject* obj = visiblePos.deepEquivalent().node()->renderer();
574 if (!obj)
575 return 0;
576
577 return obj->document()->axObjectCache()->getOrCreate(obj);
578 }
579
lineForPosition(const VisiblePosition & visiblePos) const580 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
581 {
582 if (visiblePos.isNull())
583 return 0;
584
585 unsigned lineCount = 0;
586 VisiblePosition currentVisiblePos = visiblePos;
587 VisiblePosition savedVisiblePos;
588
589 // move up until we get to the top
590 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
591 // top document.
592 while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) {
593 ++lineCount;
594 savedVisiblePos = currentVisiblePos;
595 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0);
596 currentVisiblePos = prevVisiblePos;
597 }
598
599 return lineCount - 1;
600 }
601
602 // NOTE: Consider providing this utility method as AX API
plainTextRangeForVisiblePositionRange(const VisiblePositionRange & positionRange) const603 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
604 {
605 int index1 = index(positionRange.start);
606 int index2 = index(positionRange.end);
607 if (index1 < 0 || index2 < 0 || index1 > index2)
608 return PlainTextRange();
609
610 return PlainTextRange(index1, index2 - index1);
611 }
612
613 // The composed character range in the text associated with this accessibility object that
614 // is specified by the given screen coordinates. This parameterized attribute returns the
615 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
616 // screen coordinates.
617 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
618 // an error in that case. We return textControl->text().length(), 1. Does this matter?
doAXRangeForPosition(const IntPoint & point) const619 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
620 {
621 int i = index(visiblePositionForPoint(point));
622 if (i < 0)
623 return PlainTextRange();
624
625 return PlainTextRange(i, 1);
626 }
627
628 // Given a character index, the range of text associated with this accessibility object
629 // over which the style in effect at that character index applies.
doAXStyleRangeForIndex(unsigned index) const630 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
631 {
632 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
633 return plainTextRangeForVisiblePositionRange(range);
634 }
635
636 // Given an indexed character, the line number of the text associated with this accessibility
637 // object that contains the character.
doAXLineForIndex(unsigned index)638 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
639 {
640 return lineForPosition(visiblePositionForIndex(index, false));
641 }
642
documentFrameView() const643 FrameView* AccessibilityObject::documentFrameView() const
644 {
645 const AccessibilityObject* object = this;
646 while (object && !object->isAccessibilityRenderObject())
647 object = object->parentObject();
648
649 if (!object)
650 return 0;
651
652 return object->documentFrameView();
653 }
654
clearChildren()655 void AccessibilityObject::clearChildren()
656 {
657 m_haveChildren = false;
658 m_children.clear();
659 }
660
anchorElementForNode(Node * node)661 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
662 {
663 RenderObject* obj = node->renderer();
664 if (!obj)
665 return 0;
666
667 RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj);
668 Element* anchor = axObj->anchorElement();
669 if (!anchor)
670 return 0;
671
672 RenderObject* anchorRenderer = anchor->renderer();
673 if (!anchorRenderer)
674 return 0;
675
676 return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer);
677 }
678
actionVerb() const679 const String& AccessibilityObject::actionVerb() const
680 {
681 // FIXME: Need to add verbs for select elements.
682 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
683 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
684 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
685 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
686 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
687 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
688 DEFINE_STATIC_LOCAL(const String, noAction, ());
689
690 switch (roleValue()) {
691 case ButtonRole:
692 return buttonAction;
693 case TextFieldRole:
694 case TextAreaRole:
695 return textFieldAction;
696 case RadioButtonRole:
697 return radioButtonAction;
698 case CheckBoxRole:
699 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
700 case LinkRole:
701 case WebCoreLinkRole:
702 return linkAction;
703 default:
704 return noAction;
705 }
706 }
707
708 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
orientation() const709 AccessibilityOrientation AccessibilityObject::orientation() const
710 {
711 IntRect bounds = elementRect();
712 if (bounds.size().width() > bounds.size().height())
713 return AccessibilityOrientationHorizontal;
714 if (bounds.size().height() > bounds.size().width())
715 return AccessibilityOrientationVertical;
716
717 // A tie goes to horizontal.
718 return AccessibilityOrientationHorizontal;
719 }
720
721 } // namespace WebCore
722