• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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