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