accessible/src/generic/HyperTextAccessible.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:9b00dcf99b90
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "HyperTextAccessible-inl.h"
8
9 #include "Accessible-inl.h"
10 #include "nsAccessibilityService.h"
11 #include "nsIAccessibleTypes.h"
12 #include "DocAccessible.h"
13 #include "HTMLListAccessible.h"
14 #include "Role.h"
15 #include "States.h"
16 #include "TextAttrs.h"
17 #include "TextRange.h"
18 #include "TreeWalker.h"
19
20 #include "nsCaret.h"
21 #include "nsContentUtils.h"
22 #include "nsFocusManager.h"
23 #include "nsIDOMRange.h"
24 #include "nsIEditingSession.h"
25 #include "nsIFrame.h"
26 #include "nsFrameSelection.h"
27 #include "nsILineIterator.h"
28 #include "nsIInterfaceRequestorUtils.h"
29 #include "nsIPersistentProperties2.h"
30 #include "nsIScrollableFrame.h"
31 #include "nsIServiceManager.h"
32 #include "nsITextControlElement.h"
33 #include "nsTextFragment.h"
34 #include "mozilla/dom/Element.h"
35 #include "mozilla/EventStates.h"
36 #include "mozilla/dom/Selection.h"
37 #include "mozilla/MathAlgorithms.h"
38 #include "gfxSkipChars.h"
39 #include <algorithm>
40
41 using namespace mozilla;
42 using namespace mozilla::a11y;
43
44 ////////////////////////////////////////////////////////////////////////////////
45 // HyperTextAccessible
46 ////////////////////////////////////////////////////////////////////////////////
47
48 HyperTextAccessible::
49 HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) :
50 AccessibleWrap(aNode, aDoc), xpcAccessibleHyperText()
51 {
52 mGenericTypes |= eHyperText;
53 }
54
55 nsresult
56 HyperTextAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr)
57 {
58 xpcAccessibleHyperText::QueryInterface(aIID, aInstancePtr);
59 return *aInstancePtr ? NS_OK : Accessible::QueryInterface(aIID, aInstancePtr);
60 }
61 NS_IMPL_ADDREF_INHERITED(HyperTextAccessible, AccessibleWrap)
62 NS_IMPL_RELEASE_INHERITED(HyperTextAccessible, AccessibleWrap)
63
64 role
65 HyperTextAccessible::NativeRole()
66 {
67 nsIAtom *tag = mContent->Tag();
68
69 if (tag == nsGkAtoms::dd)
70 return roles::DEFINITION;
71
72 if (tag == nsGkAtoms::form)
73 return roles::FORM;
74
75 if (tag == nsGkAtoms::blockquote || tag == nsGkAtoms::div ||
76 tag == nsGkAtoms::section || tag == nsGkAtoms::nav)
77 return roles::SECTION;
78
79 if (tag == nsGkAtoms::h1 || tag == nsGkAtoms::h2 ||
80 tag == nsGkAtoms::h3 || tag == nsGkAtoms::h4 ||
81 tag == nsGkAtoms::h5 || tag == nsGkAtoms::h6)
82 return roles::HEADING;
83
84 if (tag == nsGkAtoms::article)
85 return roles::DOCUMENT;
86
87 // Deal with html landmark elements
88 if (tag == nsGkAtoms::header)
89 return roles::HEADER;
90
91 if (tag == nsGkAtoms::footer)
92 return roles::FOOTER;
93
94 if (tag == nsGkAtoms::aside)
95 return roles::NOTE;
96
97 // Treat block frames as paragraphs
98 nsIFrame *frame = GetFrame();
99 if (frame && frame->GetType() == nsGkAtoms::blockFrame)
100 return roles::PARAGRAPH;
101
102 return roles::TEXT_CONTAINER; // In ATK this works
103 }
104
105 uint64_t
106 HyperTextAccessible::NativeState()
107 {
108 uint64_t states = AccessibleWrap::NativeState();
109
110 if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
111 states |= states::EDITABLE;
112
113 } else if (mContent->Tag() == nsGkAtoms::article) {
114 // We want <article> to behave like a document in terms of readonly state.
115 states |= states::READONLY;
116 }
117
118 if (HasChildren())
119 states |= states::SELECTABLE_TEXT;
120
121 return states;
122 }
123
124 nsIntRect
125 HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame,
126 uint32_t aStartRenderedOffset,
127 uint32_t aEndRenderedOffset)
128 {
129 nsPresContext* presContext = mDoc->PresContext();
130 if (aFrame->GetType() != nsGkAtoms::textFrame) {
131 return aFrame->GetScreenRectInAppUnits().
132 ToNearestPixels(presContext->AppUnitsPerDevPixel());
133 }
134
135 // Substring must be entirely within the same text node.
136 int32_t startContentOffset, endContentOffset;
137 nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset);
138 NS_ENSURE_SUCCESS(rv, nsIntRect());
139 rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
140 NS_ENSURE_SUCCESS(rv, nsIntRect());
141
142 nsIFrame *frame;
143 int32_t startContentOffsetInFrame;
144 // Get the right frame continuation -- not really a child, but a sibling of
145 // the primary frame passed in
146 rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false,
147 &startContentOffsetInFrame, &frame);
148 NS_ENSURE_SUCCESS(rv, nsIntRect());
149
150 nsRect screenRect;
151 while (frame && startContentOffset < endContentOffset) {
152 // Start with this frame's screen rect, which we will
153 // shrink based on the substring we care about within it.
154 // We will then add that frame to the total screenRect we
155 // are returning.
156 nsRect frameScreenRect = frame->GetScreenRectInAppUnits();
157
158 // Get the length of the substring in this frame that we want the bounds for
159 int32_t startFrameTextOffset, endFrameTextOffset;
160 frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
161 int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
162 int32_t seekLength = endContentOffset - startContentOffset;
163 int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength);
164
165 // Add the point where the string starts to the frameScreenRect
166 nsPoint frameTextStartPoint;
167 rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
168 NS_ENSURE_SUCCESS(rv, nsIntRect());
169
170 // Use the point for the end offset to calculate the width
171 nsPoint frameTextEndPoint;
172 rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
173 NS_ENSURE_SUCCESS(rv, nsIntRect());
174
175 frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x);
176 frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x);
177
178 screenRect.UnionRect(frameScreenRect, screenRect);
179
180 // Get ready to loop back for next frame continuation
181 startContentOffset += frameSubStringLength;
182 startContentOffsetInFrame = 0;
183 frame = frame->GetNextContinuation();
184 }
185
186 return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
187 }
188
189 void
190 HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
191 nsAString& aText)
192 {
193 aText.Truncate();
194
195 int32_t startOffset = ConvertMagicOffset(aStartOffset);
196 int32_t endOffset = ConvertMagicOffset(aEndOffset);
197
198 int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
199 if (startChildIdx == -1)
200 return;
201
202 int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
203 if (endChildIdx == -1)
204 return;
205
206 if (startChildIdx == endChildIdx) {
207 int32_t childOffset = GetChildOffset(startChildIdx);
208 if (childOffset == -1)
209 return;
210
211 Accessible* child = GetChildAt(startChildIdx);
212 child->AppendTextTo(aText, startOffset - childOffset,
213 endOffset - startOffset);
214 return;
215 }
216
217 int32_t startChildOffset = GetChildOffset(startChildIdx);
218 if (startChildOffset == -1)
219 return;
220
221 Accessible* startChild = GetChildAt(startChildIdx);
222 startChild->AppendTextTo(aText, startOffset - startChildOffset);
223
224 for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) {
225 Accessible* child = GetChildAt(childIdx);
226 child->AppendTextTo(aText);
227 }
228
229 int32_t endChildOffset = GetChildOffset(endChildIdx);
230 if (endChildOffset == -1)
231 return;
232
233 Accessible* endChild = GetChildAt(endChildIdx);
234 endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
235 }
236
237 int32_t
238 HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
239 bool aIsEndOffset) const
240 {
241 if (!aNode)
242 return 0;
243
244 uint32_t offset = 0;
245 nsINode* findNode = nullptr;
246
247 if (aNodeOffset == -1) {
248 findNode = aNode;
249
250 } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
251 // For text nodes, aNodeOffset comes in as a character offset
252 // Text offset will be added at the end, if we find the offset in this hypertext
253 // We want the "skipped" offset into the text (rendered text without the extra whitespace)
254 nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
255 NS_ENSURE_TRUE(frame, 0);
256
257 nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
258 NS_ENSURE_SUCCESS(rv, 0);
259 // Get the child node and
260 findNode = aNode;
261
262 } else {
263 // findNode could be null if aNodeOffset == # of child nodes, which means
264 // one of two things:
265 // 1) there are no children, and the passed-in node is not mContent -- use
266 // parentContent for the node to find
267 // 2) there are no children and the passed-in node is mContent, which means
268 // we're an empty nsIAccessibleText
269 // 3) there are children and we're at the end of the children
270
271 findNode = aNode->GetChildAt(aNodeOffset);
272 if (!findNode) {
273 if (aNodeOffset == 0) {
274 if (aNode == GetNode()) {
275 // Case #1: this accessible has no children and thus has empty text,
276 // we can only be at hypertext offset 0.
277 return 0;
278 }
279
280 // Case #2: there are no children, we're at this node.
281 findNode = aNode;
282 } else if (aNodeOffset == aNode->GetChildCount()) {
283 // Case #3: we're after the last child, get next node to this one.
284 for (nsINode* tmpNode = aNode;
285 !findNode && tmpNode && tmpNode != mContent;
286 tmpNode = tmpNode->GetParent()) {
287 findNode = tmpNode->GetNextSibling();
288 }
289 }
290 }
291 }
292
293 // Get accessible for this findNode, or if that node isn't accessible, use the
294 // accessible for the next DOM node which has one (based on forward depth first search)
295 Accessible* descendant = nullptr;
296 if (findNode) {
297 nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode));
298 if (findContent && findContent->IsHTML() &&
299 findContent->NodeInfo()->Equals(nsGkAtoms::br) &&
300 findContent->AttrValueIs(kNameSpaceID_None,
301 nsGkAtoms::mozeditorbogusnode,
302 nsGkAtoms::_true,
303 eIgnoreCase)) {
304 // This <br> is the hacky "bogus node" used when there is no text in a control
305 return 0;
306 }
307
308 descendant = mDoc->GetAccessible(findNode);
309 if (!descendant && findNode->IsContent()) {
310 Accessible* container = mDoc->GetContainerAccessible(findNode);
311 if (container) {
312 TreeWalker walker(container, findNode->AsContent(),
313 TreeWalker::eWalkContextTree);
314 descendant = walker.NextChild();
315 }
316 }
317 }
318
319 return TransformOffset(descendant, offset, aIsEndOffset);
320 }
321
322 int32_t
323 HyperTextAccessible::TransformOffset(Accessible* aDescendant,
324 int32_t aOffset, bool aIsEndOffset) const
325 {
326 // From the descendant, go up and get the immediate child of this hypertext.
327 int32_t offset = aOffset;
328 Accessible* descendant = aDescendant;
329 while (descendant) {
330 Accessible* parent = descendant->Parent();
331 if (parent == this)
332 return GetChildOffset(descendant) + offset;
333
334 // This offset no longer applies because the passed-in text object is not
335 // a child of the hypertext. This happens when there are nested hypertexts,
336 // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
337 // to make it relative the hypertext.
338 // If the end offset is not supposed to be inclusive and the original point
339 // is not at 0 offset then the returned offset should be after an embedded
340 // character the original point belongs to.
341 if (aIsEndOffset)
342 offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
343 else
344 offset = 0;
345
346 descendant = parent;
347 }
348
349 // If the given a11y point cannot be mapped into offset relative this hypertext
350 // offset then return length as fallback value.
351 return CharacterCount();
352 }
353
354 bool
355 HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
356 nsRange* aRange)
357 {
358 DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
359 if (!startPoint.node)
360 return false;
361
362 aRange->SetStart(startPoint.node, startPoint.idx);
363 if (aStartOffset == aEndOffset) {
364 aRange->SetEnd(startPoint.node, startPoint.idx);
365 return true;
366 }
367
368 DOMPoint endPoint = OffsetToDOMPoint(aEndOffset);
369 if (!endPoint.node)
370 return false;
371
372 aRange->SetEnd(endPoint.node, endPoint.idx);
373 return true;
374 }
375
376 DOMPoint
377 HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset)
378 {
379 // 0 offset is valid even if no children. In this case the associated editor
380 // is empty so return a DOM point for editor root element.
381 if (aOffset == 0) {
382 nsCOMPtr<nsIEditor> editor = GetEditor();
383 if (editor) {
384 bool isEmpty = false;
385 editor->GetDocumentIsEmpty(&isEmpty);
386 if (isEmpty) {
387 nsCOMPtr<nsIDOMElement> editorRootElm;
388 editor->GetRootElement(getter_AddRefs(editorRootElm));
389
390 nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
391 return DOMPoint(editorRoot, 0);
392 }
393 }
394 }
395
396 int32_t childIdx = GetChildIndexAtOffset(aOffset);
397 if (childIdx == -1)
398 return DOMPoint();
399
400 Accessible* child = GetChildAt(childIdx);
401 int32_t innerOffset = aOffset - GetChildOffset(childIdx);
402
403 // A text leaf case. The point is inside the text node.
404 if (child->IsTextLeaf()) {
405 nsIContent* content = child->GetContent();
406 int32_t idx = 0;
407 if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
408 innerOffset, &idx)))
409 return DOMPoint();
410
411 return DOMPoint(content, idx);
412 }
413
414 // Case of embedded object. The point is either before or after the element.
415 NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
416 nsINode* node = child->GetNode();
417 nsINode* parentNode = node->GetParentNode();
418 return parentNode ?
419 DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
420 DOMPoint();
421 }
422
423 int32_t
424 HyperTextAccessible::FindOffset(int32_t aOffset, nsDirection aDirection,
425 nsSelectionAmount aAmount,
426 EWordMovementType aWordMovementType)
427 {
428 // Find a leaf accessible frame to start with. PeekOffset wants this.
429 HyperTextAccessible* text = this;
430 Accessible* child = nullptr;
431 int32_t innerOffset = aOffset;
432
433 do {
434 int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
435 NS_ASSERTION(childIdx != -1, "Bad in offset!");
436 if (childIdx == -1)
437 return -1;
438
439 child = text->GetChildAt(childIdx);
440
441 // HTML list items may need special processing because PeekOffset doesn't
442 // work with list bullets.
443 if (text->IsHTMLListItem()) {
444 HTMLLIAccessible* li = text->AsHTMLListItem();
445 if (child == li->Bullet()) {
446 // It works only when the bullet is one single char.
447 if (aDirection == eDirPrevious)
448 return text != this ? TransformOffset(text, 0, false) : 0;
449
450 if (aAmount == eSelectEndLine || aAmount == eSelectLine) {
451 if (text != this)
452 return TransformOffset(text, 1, true);
453
454 // Ask a text leaf next (if not empty) to the bullet for an offset
455 // since list item may be multiline.
456 return aOffset + 1 < CharacterCount() ?
457 FindOffset(aOffset + 1, aDirection, aAmount, aWordMovementType) : 1;
458 }
459
460 // Case of word and char boundaries.
461 return text != this ? TransformOffset(text, 1, true) : 1;
462 }
463 }
464
465 innerOffset -= text->GetChildOffset(childIdx);
466
467 text = child->AsHyperText();
468 } while (text);
469
470 nsIFrame* childFrame = child->GetFrame();
471 NS_ENSURE_TRUE(childFrame, -1);
472
473 int32_t innerContentOffset = innerOffset;
474 if (child->IsTextLeaf()) {
475 NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!");
476 RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
477 }
478
479 nsIFrame* frameAtOffset = childFrame;
480 int32_t unusedOffsetInFrame = 0;
481 childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
482 &unusedOffsetInFrame,
483 &frameAtOffset);
484
485 const bool kIsJumpLinesOk = true; // okay to jump lines
486 const bool kIsScrollViewAStop = false; // do not stop at scroll views
487 const bool kIsKeyboardSelect = true; // is keyboard selection
488 const bool kIsVisualBidi = false; // use visual order for bidi text
489 nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset,
490 0, kIsJumpLinesOk, kIsScrollViewAStop,
491 kIsKeyboardSelect, kIsVisualBidi,
492 aWordMovementType);
493 nsresult rv = frameAtOffset->PeekOffset(&pos);
494
495 // PeekOffset fails on last/first lines of the text in certain cases.
496 if (NS_FAILED(rv) && aAmount == eSelectLine) {
497 pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
498 frameAtOffset->PeekOffset(&pos);
499 }
500 if (!pos.mResultContent)
501 return -1;
502
503 // Turn the resulting DOM point into an offset.
504 int32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
505 pos.mContentOffset,
506 aDirection == eDirNext);
507
508 if (aDirection == eDirPrevious) {
509 // If we reached the end during search, this means we didn't find the DOM point
510 // and we're actually at the start of the paragraph
511 if (hyperTextOffset == CharacterCount())
512 return 0;
513
514 // PeekOffset stops right before bullet so return 0 to workaround it.
515 if (IsHTMLListItem() && aAmount == eSelectBeginLine && hyperTextOffset == 1)
516 return 0;
517 }
518
519 return hyperTextOffset;
520 }
521
522 int32_t
523 HyperTextAccessible::FindLineBoundary(int32_t aOffset,
524 EWhichLineBoundary aWhichLineBoundary)
525 {
526 // Note: empty last line doesn't have own frame (a previous line contains '\n'
527 // character instead) thus when it makes a difference we need to process this
528 // case separately (otherwise operations are performed on previous line).
529 switch (aWhichLineBoundary) {
530 case ePrevLineBegin: {
531 // Fetch a previous line and move to its start (as arrow up and home keys
532 // were pressed).
533 if (IsEmptyLastLineOffset(aOffset))
534 return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
535
536 int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
537 return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
538 }
539
540 case ePrevLineEnd: {
541 if (IsEmptyLastLineOffset(aOffset))
542 return aOffset - 1;
543
544 // If offset is at first line then return 0 (first line start).
545 int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
546 if (tmpOffset == 0)
547 return 0;
548
549 // Otherwise move to end of previous line (as arrow up and end keys were
550 // pressed).
551 tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
552 return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
553 }
554
555 case eThisLineBegin:
556 if (IsEmptyLastLineOffset(aOffset))
557 return aOffset;
558
559 // Move to begin of the current line (as home key was pressed).
560 return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
561
562 case eThisLineEnd:
563 if (IsEmptyLastLineOffset(aOffset))
564 return aOffset;
565
566 // Move to end of the current line (as end key was pressed).
567 return FindOffset(aOffset, eDirNext, eSelectEndLine);
568
569 case eNextLineBegin: {
570 if (IsEmptyLastLineOffset(aOffset))
571 return aOffset;
572
573 // Move to begin of the next line if any (arrow down and home keys),
574 // otherwise end of the current line (arrow down only).
575 int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
576 if (tmpOffset == CharacterCount())
577 return tmpOffset;
578
579 return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
580 }
581
582 case eNextLineEnd: {
583 if (IsEmptyLastLineOffset(aOffset))
584 return aOffset;
585
586 // Move to next line end (as down arrow and end key were pressed).
587 int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
588 if (tmpOffset != CharacterCount())
589 return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
590 return tmpOffset;
591 }
592 }
593
594 return -1;
595 }
596
597 void
598 HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
599 AccessibleTextBoundary aBoundaryType,
600 int32_t* aStartOffset, int32_t* aEndOffset,
601 nsAString& aText)
602 {
603 *aStartOffset = *aEndOffset = 0;
604 aText.Truncate();
605
606 int32_t convertedOffset = ConvertMagicOffset(aOffset);
607 if (convertedOffset < 0) {
608 NS_ERROR("Wrong given offset!");
609 return;
610 }
611
612 int32_t adjustedOffset = convertedOffset;
613 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
614 adjustedOffset = AdjustCaretOffset(adjustedOffset);
615
616 switch (aBoundaryType) {
617 case BOUNDARY_CHAR:
618 if (convertedOffset != 0)
619 CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset);
620 break;
621
622 case BOUNDARY_WORD_START: {
623 // If the offset is a word start (except text length offset) then move
624 // backward to find a start offset (end offset is the given offset).
625 // Otherwise move backward twice to find both start and end offsets.
626 if (adjustedOffset == CharacterCount()) {
627 *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
628 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
629 } else {
630 *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
631 *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
632 if (*aEndOffset != adjustedOffset) {
633 *aEndOffset = *aStartOffset;
634 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
635 }
636 }
637 TextSubstring(*aStartOffset, *aEndOffset, aText);
638 break;
639 }
640
641 case BOUNDARY_WORD_END: {
642 // Move word backward twice to find start and end offsets.
643 *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord);
644 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
645 TextSubstring(*aStartOffset, *aEndOffset, aText);
646 break;
647 }
648
649 case BOUNDARY_LINE_START:
650 *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin);
651 *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
652 TextSubstring(*aStartOffset, *aEndOffset, aText);
653 break;
654
655 case BOUNDARY_LINE_END: {
656 *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
657 int32_t tmpOffset = *aEndOffset;
658 // Adjust offset if line is wrapped.
659 if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset))
660 tmpOffset--;
661
662 *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd);
663 TextSubstring(*aStartOffset, *aEndOffset, aText);
664 break;
665 }
666 }
667 }
668
669 void
670 HyperTextAccessible::TextAtOffset(int32_t aOffset,
671 AccessibleTextBoundary aBoundaryType,
672 int32_t* aStartOffset, int32_t* aEndOffset,
673 nsAString& aText)
674 {
675 *aStartOffset = *aEndOffset = 0;
676 aText.Truncate();
677
678 int32_t adjustedOffset = ConvertMagicOffset(aOffset);
679 if (adjustedOffset < 0) {
680 NS_ERROR("Wrong given offset!");
681 return;
682 }
683
684 switch (aBoundaryType) {
685 case BOUNDARY_CHAR:
686 // Return no char if caret is at the end of wrapped line (case of no line
687 // end character). Returning a next line char is confusing for AT.
688 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine())
689 *aStartOffset = *aEndOffset = adjustedOffset;
690 else
691 CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
692 break;
693
694 case BOUNDARY_WORD_START:
695 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
696 adjustedOffset = AdjustCaretOffset(adjustedOffset);
697
698 *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
699 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
700 TextSubstring(*aStartOffset, *aEndOffset, aText);
701 break;
702
703 case BOUNDARY_WORD_END:
704 // Ignore the spec and follow what WebKitGtk does because Orca expects it,
705 // i.e. return a next word at word end offset of the current word
706 // (WebKitGtk behavior) instead the current word (AKT spec).
707 *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
708 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
709 TextSubstring(*aStartOffset, *aEndOffset, aText);
710 break;
711
712 case BOUNDARY_LINE_START:
713 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
714 adjustedOffset = AdjustCaretOffset(adjustedOffset);
715
716 *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
717 *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
718 TextSubstring(*aStartOffset, *aEndOffset, aText);
719 break;
720
721 case BOUNDARY_LINE_END:
722 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
723 adjustedOffset = AdjustCaretOffset(adjustedOffset);
724
725 // In contrast to word end boundary we follow the spec here.
726 *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
727 *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
728 TextSubstring(*aStartOffset, *aEndOffset, aText);
729 break;
730 }
731 }
732
733 void
734 HyperTextAccessible::TextAfterOffset(int32_t aOffset,
735 AccessibleTextBoundary aBoundaryType,
736 int32_t* aStartOffset, int32_t* aEndOffset,
737 nsAString& aText)
738 {
739 *aStartOffset = *aEndOffset = 0;
740 aText.Truncate();
741
742 int32_t convertedOffset = ConvertMagicOffset(aOffset);
743 if (convertedOffset < 0) {
744 NS_ERROR("Wrong given offset!");
745 return;
746 }
747
748 int32_t adjustedOffset = convertedOffset;
749 if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
750 adjustedOffset = AdjustCaretOffset(adjustedOffset);
751
752 switch (aBoundaryType) {
753 case BOUNDARY_CHAR:
754 // If caret is at the end of wrapped line (case of no line end character)
755 // then char after the offset is a first char at next line.
756 if (adjustedOffset >= CharacterCount())
757 *aStartOffset = *aEndOffset = CharacterCount();
758 else
759 CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset);
760 break;
761
762 case BOUNDARY_WORD_START:
763 // Move word forward twice to find start and end offsets.
764 *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
765 *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
766 TextSubstring(*aStartOffset, *aEndOffset, aText);
767 break;
768
769 case BOUNDARY_WORD_END:
770 // If the offset is a word end (except 0 offset) then move forward to find
771 // end offset (start offset is the given offset). Otherwise move forward
772 // twice to find both start and end offsets.
773 if (convertedOffset == 0) {
774 *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
775 *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
776 } else {
777 *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
778 *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
779 if (*aStartOffset != convertedOffset) {
780 *aStartOffset = *aEndOffset;
781 *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
782 }
783 }
784 TextSubstring(*aStartOffset, *aEndOffset, aText);
785 break;
786
787 case BOUNDARY_LINE_START:
788 *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
789 *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
790 TextSubstring(*aStartOffset, *aEndOffset, aText);
791 break;
792
793 case BOUNDARY_LINE_END:
794 *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
795 *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd);
796 TextSubstring(*aStartOffset, *aEndOffset, aText);
797 break;
798 }
799 }
800
801 already_AddRefed<nsIPersistentProperties>
802 HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
803 int32_t* aStartOffset,
804 int32_t* aEndOffset)
805 {
806 // 1. Get each attribute and its ranges one after another.
807 // 2. As we get each new attribute, we pass the current start and end offsets
808 // as in/out parameters. In other words, as attributes are collected,
809 // the attribute range itself can only stay the same or get smaller.
810
811 *aStartOffset = *aEndOffset = 0;
812 nsCOMPtr<nsIPersistentProperties> attributes =
813 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
814
815 int32_t offset = ConvertMagicOffset(aOffset);
816 Accessible* accAtOffset = GetChildAtOffset(offset);
817 if (!accAtOffset) {
818 // Offset 0 is correct offset when accessible has empty text. Include
819 // default attributes if they were requested, otherwise return empty set.
820 if (offset == 0) {
821 if (aIncludeDefAttrs) {
822 TextAttrsMgr textAttrsMgr(this);
823 textAttrsMgr.GetAttributes(attributes);
824 }
825 return attributes.forget();
826 }
827 return nullptr;
828 }
829
830 int32_t accAtOffsetIdx = accAtOffset->IndexInParent();
831 int32_t startOffset = GetChildOffset(accAtOffsetIdx);
832 int32_t endOffset = GetChildOffset(accAtOffsetIdx + 1);
833 int32_t offsetInAcc = offset - startOffset;
834
835 TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset,
836 accAtOffsetIdx);
837 textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset);
838
839 // Compute spelling attributes on text accessible only.
840 nsIFrame *offsetFrame = accAtOffset->GetFrame();
841 if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) {
842 int32_t nodeOffset = 0;
843 RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset);
844
845 // Set 'misspelled' text attribute.
846 GetSpellTextAttribute(accAtOffset->GetNode(), nodeOffset,
847 &startOffset, &endOffset, attributes);
848 }
849
850 *aStartOffset = startOffset;
851 *aEndOffset = endOffset;
852 return attributes.forget();
853 }
854
855 already_AddRefed<nsIPersistentProperties>
856 HyperTextAccessible::DefaultTextAttributes()
857 {
858 nsCOMPtr<nsIPersistentProperties> attributes =
859 do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
860
861 TextAttrsMgr textAttrsMgr(this);
862 textAttrsMgr.GetAttributes(attributes);
863 return attributes.forget();
864 }
865
866 int32_t
867 HyperTextAccessible::GetLevelInternal()
868 {
869 nsIAtom *tag = mContent->Tag();
870 if (tag == nsGkAtoms::h1)
871 return 1;
872 if (tag == nsGkAtoms::h2)
873 return 2;
874 if (tag == nsGkAtoms::h3)
875 return 3;
876 if (tag == nsGkAtoms::h4)
877 return 4;
878 if (tag == nsGkAtoms::h5)
879 return 5;
880 if (tag == nsGkAtoms::h6)
881 return 6;
882
883 return AccessibleWrap::GetLevelInternal();
884 }
885
886 already_AddRefed<nsIPersistentProperties>
887 HyperTextAccessible::NativeAttributes()
888 {
889 nsCOMPtr<nsIPersistentProperties> attributes =
890 AccessibleWrap::NativeAttributes();
891
892 // 'formatting' attribute is deprecated, 'display' attribute should be
893 // instead.
894 nsIFrame *frame = GetFrame();
895 if (frame && frame->GetType() == nsGkAtoms::blockFrame) {
896 nsAutoString unused;
897 attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"),
898 NS_LITERAL_STRING("block"), unused);
899 }
900
901 if (FocusMgr()->IsFocused(this)) {
902 int32_t lineNumber = CaretLineNumber();
903 if (lineNumber >= 1) {
904 nsAutoString strLineNumber;
905 strLineNumber.AppendInt(lineNumber);
906 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber);
907 }
908 }
909
910 if (!HasOwnContent())
911 return attributes.forget();
912
913 // For the html landmark elements we expose them like we do aria landmarks to
914 // make AT navigation schemes "just work".
915 nsIAtom* tag = mContent->Tag();
916 if (tag == nsGkAtoms::nav) {
917 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
918 NS_LITERAL_STRING("navigation"));
919 } else if (tag == nsGkAtoms::section) {
920 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
921 NS_LITERAL_STRING("region"));
922 } else if (tag == nsGkAtoms::header || tag == nsGkAtoms::footer) {
923 // Only map header and footer if they are not descendants
924 // of an article or section tag.
925 nsIContent* parent = mContent->GetParent();
926 while (parent) {
927 if (parent->Tag() == nsGkAtoms::article ||
928 parent->Tag() == nsGkAtoms::section)
929 break;
930 parent = parent->GetParent();
931 }
932
933 // No article or section elements found.
934 if (!parent) {
935 if (tag == nsGkAtoms::header) {
936 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
937 NS_LITERAL_STRING("banner"));
938 } else if (tag == nsGkAtoms::footer) {
939 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
940 NS_LITERAL_STRING("contentinfo"));
941 }
942 }
943 } else if (tag == nsGkAtoms::aside) {
944 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
945 NS_LITERAL_STRING("complementary"));
946 } else if (tag == nsGkAtoms::article) {
947 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
948 NS_LITERAL_STRING("article"));
949 } else if (tag == nsGkAtoms::main) {
950 nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
951 NS_LITERAL_STRING("main"));
952 }
953
954 return attributes.forget();
955 }
956
957 int32_t
958 HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
959 {
960 nsIFrame* hyperFrame = GetFrame();
961 if (!hyperFrame)
962 return -1;
963
964 nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType,
965 this);
966
967 nsPresContext* presContext = mDoc->PresContext();
968 nsPoint coordsInAppUnits =
969 coords.ToAppUnits(presContext->AppUnitsPerDevPixel());
970
971 nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
972 if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y))
973 return -1; // Not found
974
975 nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x,
976 coordsInAppUnits.y - frameScreenRect.y);
977
978 // Go through the frames to check if each one has the point.
979 // When one does, add up the character offsets until we have a match
980
981 // We have an point in an accessible child of this, now we need to add up the
982 // offsets before it to what we already have
983 int32_t offset = 0;
984 uint32_t childCount = ChildCount();
985 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
986 Accessible* childAcc = mChildren[childIdx];
987
988 nsIFrame *primaryFrame = childAcc->GetFrame();
989 NS_ENSURE_TRUE(primaryFrame, -1);
990
991 nsIFrame *frame = primaryFrame;
992 while (frame) {
993 nsIContent *content = frame->GetContent();
994 NS_ENSURE_TRUE(content, -1);
995 nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
996 nsSize frameSize = frame->GetSize();
997 if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
998 // Finished
999 if (frame->GetType() == nsGkAtoms::textFrame) {
1000 nsIFrame::ContentOffsets contentOffsets =
1001 frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
1002 if (contentOffsets.IsNull() || contentOffsets.content != content) {
1003 return -1; // Not found
1004 }
1005 uint32_t addToOffset;
1006 nsresult rv = ContentToRenderedOffset(primaryFrame,
1007 contentOffsets.offset,
1008 &addToOffset);
1009 NS_ENSURE_SUCCESS(rv, -1);
1010 offset += addToOffset;
1011 }
1012 return offset;
1013 }
1014 frame = frame->GetNextContinuation();
1015 }
1016
1017 offset += nsAccUtils::TextLength(childAcc);
1018 }
1019
1020 return -1; // Not found
1021 }
1022
1023 nsIntRect
1024 HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
1025 uint32_t aCoordType)
1026 {
1027 int32_t startOffset = ConvertMagicOffset(aStartOffset);
1028 int32_t endOffset = ConvertMagicOffset(aEndOffset);
1029 NS_ASSERTION(startOffset < endOffset, "Wrong bad in!");
1030
1031 int32_t childIdx = GetChildIndexAtOffset(startOffset);
1032 if (childIdx == -1)
1033 return nsIntRect();
1034
1035 nsIntRect bounds;
1036 int32_t prevOffset = GetChildOffset(childIdx);
1037 int32_t offset1 = startOffset - prevOffset;
1038
1039 while (childIdx < ChildCount()) {
1040 nsIFrame* frame = GetChildAt(childIdx++)->GetFrame();
1041 if (!frame) {
1042 NS_NOTREACHED("No frame for a child!");
1043 continue;
1044 }
1045
1046 int32_t nextOffset = GetChildOffset(childIdx);
1047 if (nextOffset >= endOffset) {
1048 bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
1049 endOffset - prevOffset));
1050 break;
1051 }
1052
1053 bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
1054 nextOffset - prevOffset));
1055
1056 prevOffset = nextOffset;
1057 offset1 = 0;
1058 }
1059
1060 nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this);
1061 return bounds;
1062 }
1063
1064 already_AddRefed<nsIEditor>
1065 HyperTextAccessible::GetEditor() const
1066 {
1067 if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
1068 // If we're inside an editable container, then return that container's editor
1069 Accessible* ancestor = Parent();
1070 while (ancestor) {
1071 HyperTextAccessible* hyperText = ancestor->AsHyperText();
1072 if (hyperText) {
1073 // Recursion will stop at container doc because it has its own impl
1074 // of GetEditor()
1075 return hyperText->GetEditor();
1076 }
1077
1078 ancestor = ancestor->Parent();
1079 }
1080
1081 return nullptr;
1082 }
1083
1084 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
1085 nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(docShell));
1086 if (!editingSession)
1087 return nullptr; // No editing session interface
1088
1089 nsCOMPtr<nsIEditor> editor;
1090 nsIDocument* docNode = mDoc->DocumentNode();
1091 editingSession->GetEditorForWindow(docNode->GetWindow(),
1092 getter_AddRefs(editor));
1093 return editor.forget();
1094 }
1095
1096 /**
1097 * =================== Caret & Selection ======================
1098 */
1099
1100 nsresult
1101 HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos)
1102 {
1103 // Before setting the selection range, we need to ensure that the editor
1104 // is initialized. (See bug 804927.)
1105 // Otherwise, it's possible that lazy editor initialization will override
1106 // the selection we set here and leave the caret at the end of the text.
1107 // By calling GetEditor here, we ensure that editor initialization is
1108 // completed before we set the selection.
1109 nsCOMPtr<nsIEditor> editor = GetEditor();
1110
1111 bool isFocusable = InteractiveState() & states::FOCUSABLE;
1112
1113 // If accessible is focusable then focus it before setting the selection to
1114 // neglect control's selection changes on focus if any (for example, inputs
1115 // that do select all on focus).
1116 // some input controls
1117 if (isFocusable)
1118 TakeFocus();
1119
1120 dom::Selection* domSel = DOMSelection();
1121 NS_ENSURE_STATE(domSel);
1122
1123 // Set up the selection.
1124 for (int32_t idx = domSel->GetRangeCount() - 1; idx > 0; idx--)
1125 domSel->RemoveRange(domSel->GetRangeAt(idx));
1126 SetSelectionBoundsAt(0, aStartPos, aEndPos);
1127
1128 // When selection is done, move the focus to the selection if accessible is
1129 // not focusable. That happens when selection is set within hypertext
1130 // accessible.
1131 if (isFocusable)
1132 return NS_OK;
1133
1134 nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
1135 if (DOMFocusManager) {
1136 NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
1137 nsIDocument* docNode = mDoc->DocumentNode();
1138 NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
1139 nsCOMPtr<nsPIDOMWindow> window = docNode->GetWindow();
1140 nsCOMPtr<nsIDOMElement> result;
1141 DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
1142 nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result));
1143 }
1144
1145 return NS_OK;
1146 }
1147
1148 int32_t
1149 HyperTextAccessible::CaretOffset() const
1150 {
1151 // Not focused focusable accessible except document accessible doesn't have
1152 // a caret.
1153 if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
1154 (InteractiveState() & states::FOCUSABLE)) {
1155 return -1;
1156 }
1157
1158 // No caret if the focused node is not inside this DOM node and this DOM node
1159 // is not inside of focused node.
1160 FocusManager::FocusDisposition focusDisp =
1161 FocusMgr()->IsInOrContainsFocus(this);
1162 if (focusDisp == FocusManager::eNone)
1163 return -1;
1164
1165 // Turn the focus node and offset of the selection into caret hypretext
1166 // offset.
1167 dom::Selection* domSel = DOMSelection();
1168 NS_ENSURE_TRUE(domSel, -1);
1169
1170 nsINode* focusNode = domSel->GetFocusNode();
1171 uint32_t focusOffset = domSel->FocusOffset();
1172
1173 // No caret if this DOM node is inside of focused node but the selection's
1174 // focus point is not inside of this DOM node.
1175 if (focusDisp == FocusManager::eContainedByFocus) {
1176 nsINode* resultNode =
1177 nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
1178
1179 nsINode* thisNode = GetNode();
1180 if (resultNode != thisNode &&
1181 !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
1182 return -1;
1183 }
1184
1185 return DOMPointToOffset(focusNode, focusOffset);
1186 }
1187
1188 int32_t
1189 HyperTextAccessible::CaretLineNumber()
1190 {
1191 // Provide the line number for the caret, relative to the
1192 // currently focused node. Use a 1-based index
1193 nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
1194 if (!frameSelection)
1195 return -1;
1196
1197 dom::Selection* domSel =
1198 frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
1199 if (!domSel)
1200 return - 1;
1201
1202 nsINode* caretNode = domSel->GetFocusNode();
1203 if (!caretNode || !caretNode->IsContent())
1204 return -1;
1205
1206 nsIContent* caretContent = caretNode->AsContent();
1207 if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent))
1208 return -1;
1209
1210 int32_t returnOffsetUnused;
1211 uint32_t caretOffset = domSel->FocusOffset();
1212 nsFrameSelection::HINT hint = frameSelection->GetHint();
1213 nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
1214 hint, &returnOffsetUnused);
1215 NS_ENSURE_TRUE(caretFrame, -1);
1216
1217 int32_t lineNumber = 1;
1218 nsAutoLineIterator lineIterForCaret;
1219 nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr;
1220 while (caretFrame) {
1221 if (hyperTextContent == caretFrame->GetContent()) {
1222 return lineNumber; // Must be in a single line hyper text, there is no line iterator
1223 }
1224 nsIFrame *parentFrame = caretFrame->GetParent();
1225 if (!parentFrame)
1226 break;
1227
1228 // Add lines for the sibling frames before the caret
1229 nsIFrame *sibling = parentFrame->GetFirstPrincipalChild();
1230 while (sibling && sibling != caretFrame) {
1231 nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
1232 if (lineIterForSibling) {
1233 // For the frames before that grab all the lines
1234 int32_t addLines = lineIterForSibling->GetNumLines();
1235 lineNumber += addLines;
1236 }
1237 sibling = sibling->GetNextSibling();
1238 }
1239
1240 // Get the line number relative to the container with lines
1241 if (!lineIterForCaret) { // Add the caret line just once
1242 lineIterForCaret = parentFrame->GetLineIterator();
1243 if (lineIterForCaret) {
1244 // Ancestor of caret
1245 int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame);
1246 lineNumber += addLines;
1247 }
1248 }
1249
1250 caretFrame = parentFrame;
1251 }
1252
1253 NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
1254 return lineNumber;
1255 }
1256
1257 nsIntRect
1258 HyperTextAccessible::GetCaretRect(nsIWidget** aWidget)
1259 {
1260 *aWidget = nullptr;
1261
1262 nsRefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret();
1263 NS_ENSURE_TRUE(caret, nsIntRect());
1264
1265 nsISelection* caretSelection = caret->GetCaretDOMSelection();
1266 NS_ENSURE_TRUE(caretSelection, nsIntRect());
1267
1268 bool isVisible = false;
1269 caret->GetCaretVisible(&isVisible);
1270 if (!isVisible)
1271 return nsIntRect();
1272
1273 nsRect rect;
1274 nsIFrame* frame = caret->GetGeometry(caretSelection, &rect);
1275 if (!frame || rect.IsEmpty())
1276 return nsIntRect();
1277
1278 nsPoint offset;
1279 // Offset from widget origin to the frame origin, which includes chrome
1280 // on the widget.
1281 *aWidget = frame->GetNearestWidget(offset);
1282 NS_ENSURE_TRUE(*aWidget, nsIntRect());
1283 rect.MoveBy(offset);
1284
1285 nsIntRect caretRect;
1286 caretRect = rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel());
1287 // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
1288 caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset());
1289
1290 // Correct for character size, so that caret always matches the size of
1291 // the character. This is important for font size transitions, and is
1292 // necessary because the Gecko caret uses the previous character's size as
1293 // the user moves forward in the text by character.
1294 nsIntRect charRect = CharBounds(CaretOffset(),
1295 nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
1296 if (!charRect.IsEmpty()) {
1297 caretRect.height -= charRect.y - caretRect.y;
1298 caretRect.y = charRect.y;
1299 }
1300 return caretRect;
1301 }
1302
1303 void
1304 HyperTextAccessible::GetSelectionDOMRanges(int16_t aType,
1305 nsTArray<nsRange*>* aRanges)
1306 {
1307 // Ignore selection if it is not visible.
1308 nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
1309 if (!frameSelection ||
1310 frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN)
1311 return;
1312
1313 dom::Selection* domSel = frameSelection->GetSelection(aType);
1314 if (!domSel)
1315 return;
1316
1317 nsCOMPtr<nsINode> startNode = GetNode();
1318
1319 nsCOMPtr<nsIEditor> editor = GetEditor();
1320 if (editor) {
1321 nsCOMPtr<nsIDOMElement> editorRoot;
1322 editor->GetRootElement(getter_AddRefs(editorRoot));
1323 startNode = do_QueryInterface(editorRoot);
1324 }
1325
1326 if (!startNode)
1327 return;
1328
1329 uint32_t childCount = startNode->GetChildCount();
1330 nsresult rv = domSel->
1331 GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges);
1332 NS_ENSURE_SUCCESS_VOID(rv);
1333
1334 // Remove collapsed ranges
1335 uint32_t numRanges = aRanges->Length();
1336 for (uint32_t idx = 0; idx < numRanges; idx ++) {
1337 if ((*aRanges)[idx]->Collapsed()) {
1338 aRanges->RemoveElementAt(idx);
1339 --numRanges;
1340 --idx;
1341 }
1342 }
1343 }
1344
1345 int32_t
1346 HyperTextAccessible::SelectionCount()
1347 {
1348 nsTArray<nsRange*> ranges;
1349 GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges);
1350 return ranges.Length();
1351 }
1352
1353 bool
1354 HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
1355 int32_t* aStartOffset,
1356 int32_t* aEndOffset)
1357 {
1358 *aStartOffset = *aEndOffset = 0;
1359
1360 nsTArray<nsRange*> ranges;
1361 GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges);
1362
1363 uint32_t rangeCount = ranges.Length();
1364 if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
1365 return false;
1366
1367 nsRange* range = ranges[aSelectionNum];
1368
1369 // Get start and end points.
1370 nsINode* startNode = range->GetStartParent();
1371 nsINode* endNode = range->GetEndParent();
1372 int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset();
1373
1374 // Make sure start is before end, by swapping DOM points. This occurs when
1375 // the user selects backwards in the text.
1376 int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset,
1377 startNode, startOffset);
1378 if (rangeCompare < 0) {
1379 nsINode* tempNode = startNode;
1380 startNode = endNode;
1381 endNode = tempNode;
1382 int32_t tempOffset = startOffset;
1383 startOffset = endOffset;
1384 endOffset = tempOffset;
1385 }
1386
1387 *aStartOffset = DOMPointToOffset(startNode, startOffset);
1388 *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
1389 return true;
1390 }
1391
1392 bool
1393 HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
1394 int32_t aStartOffset,
1395 int32_t aEndOffset)
1396 {
1397 int32_t startOffset = ConvertMagicOffset(aStartOffset);
1398 int32_t endOffset = ConvertMagicOffset(aEndOffset);
1399
1400 dom::Selection* domSel = DOMSelection();
1401 if (!domSel)
1402 return false;
1403
1404 nsRefPtr<nsRange> range;
1405 uint32_t rangeCount = domSel->GetRangeCount();
1406 if (aSelectionNum == rangeCount)
1407 range = new nsRange(mContent);
1408 else
1409 range = domSel->GetRangeAt(aSelectionNum);
1410
1411 if (!range)
1412 return false;
1413
1414 if (!OffsetsToDOMRange(startOffset, endOffset, range))
1415 return false;
1416
1417 // If new range was created then add it, otherwise notify selection listeners
1418 // that existing selection range was changed.
1419 if (aSelectionNum == rangeCount)
1420 return NS_SUCCEEDED(domSel->AddRange(range));
1421
1422 domSel->RemoveRange(range);
1423 return NS_SUCCEEDED(domSel->AddRange(range));
1424 }
1425
1426 bool
1427 HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum)
1428 {
1429 dom::Selection* domSel = DOMSelection();
1430 if (!domSel)
1431 return false;
1432
1433 if (aSelectionNum < 0 || aSelectionNum >= domSel->GetRangeCount())
1434 return false;
1435
1436 domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum));
1437 return true;
1438 }
1439
1440 void
1441 HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
1442 uint32_t aScrollType)
1443 {
1444 nsRefPtr<nsRange> range = new nsRange(mContent);
1445 if (OffsetsToDOMRange(aStartOffset, aEndOffset, range))
1446 nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
1447 }
1448
1449 void
1450 HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
1451 int32_t aEndOffset,
1452 uint32_t aCoordinateType,
1453 int32_t aX, int32_t aY)
1454 {
1455 nsIFrame *frame = GetFrame();
1456 if (!frame)
1457 return;
1458
1459 nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
1460 this);
1461
1462 nsRefPtr<nsRange> range = new nsRange(mContent);
1463 if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range))
1464 return;
1465
1466 nsPresContext* presContext = frame->PresContext();
1467 nsPoint coordsInAppUnits =
1468 coords.ToAppUnits(presContext->AppUnitsPerDevPixel());
1469
1470 bool initialScrolled = false;
1471 nsIFrame *parentFrame = frame;
1472 while ((parentFrame = parentFrame->GetParent())) {
1473 nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame);
1474 if (scrollableFrame) {
1475 if (!initialScrolled) {
1476 // Scroll substring to the given point. Turn the point into percents
1477 // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
1478 nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
1479 nscoord offsetPointX = coordsInAppUnits.x - frameRect.x;
1480 nscoord offsetPointY = coordsInAppUnits.y - frameRect.y;
1481
1482 nsSize size(parentFrame->GetSize());
1483
1484 // avoid divide by zero
1485 size.width = size.width ? size.width : 1;
1486 size.height = size.height ? size.height : 1;
1487
1488 int16_t hPercent = offsetPointX * 100 / size.width;
1489 int16_t vPercent = offsetPointY * 100 / size.height;
1490
1491 nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range, vPercent, hPercent);
1492 if (NS_FAILED(rv))
1493 return;
1494
1495 initialScrolled = true;
1496 } else {
1497 // Substring was scrolled to the given point already inside its closest
1498 // scrollable area. If there are nested scrollable areas then make
1499 // sure we scroll lower areas to the given point inside currently
1500 // traversed scrollable area.
1501 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
1502 }
1503 }
1504 frame = parentFrame;
1505 }
1506 }
1507
1508 void
1509 HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const
1510 {
1511 if (IsTextField()) {
1512 aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0,
1513 const_cast<HyperTextAccessible*>(this), ChildCount());
1514 } else {
1515 aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->ChildCount());
1516 }
1517 }
1518
1519 void
1520 HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const
1521 {
1522 NS_ASSERTION(aRanges->Length() != 0, "TextRange array supposed to be empty");
1523
1524 dom::Selection* sel = DOMSelection();
1525 if (!sel)
1526 return;
1527
1528 aRanges->SetCapacity(sel->RangeCount());
1529
1530 for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
1531 nsRange* DOMRange = sel->GetRangeAt(idx);
1532 HyperTextAccessible* startParent =
1533 nsAccUtils::GetTextContainer(DOMRange->GetStartParent());
1534 HyperTextAccessible* endParent =
1535 nsAccUtils::GetTextContainer(DOMRange->GetEndParent());
1536 if (!startParent || !endParent)
1537 continue;
1538
1539 int32_t startOffset =
1540 startParent->DOMPointToOffset(DOMRange->GetStartParent(),
1541 DOMRange->StartOffset(), false);
1542 int32_t endOffset =
1543 endParent->DOMPointToOffset(DOMRange->GetEndParent(),
1544 DOMRange->EndOffset(), true);
1545
1546 TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc,
1547 startParent, startOffset, endParent, endOffset);
1548 *(aRanges->AppendElement()) = Move(tr);
1549 }
1550 }
1551
1552 void
1553 HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const
1554 {
1555 }
1556
1557 void
1558 HyperTextAccessible::RangeByChild(Accessible* aChild,
1559 a11y::TextRange& aRange) const
1560 {
1561 aRange.Set(mDoc, aChild, 0, aChild, aChild->ChildCount());
1562 }
1563
1564 void
1565 HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY,
1566 a11y::TextRange& aRange) const
1567 {
1568 Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild);
1569 if (child)
1570 aRange.Set(mDoc, child, 0, child, child->ChildCount());
1571 }
1572
1573 ////////////////////////////////////////////////////////////////////////////////
1574 // Accessible public
1575
1576 // Accessible protected
1577 ENameValueFlag
1578 HyperTextAccessible::NativeName(nsString& aName)
1579 {
1580 // Check @alt attribute for invalid img elements.
1581 bool hasImgAlt = false;
1582 if (mContent->IsHTML(nsGkAtoms::img)) {
1583 hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
1584 if (!aName.IsEmpty())
1585 return eNameOK;
1586 }
1587
1588 ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
1589 if (!aName.IsEmpty())
1590 return nameFlag;
1591
1592 // Get name from title attribute for HTML abbr and acronym elements making it
1593 // a valid name from markup. Otherwise their name isn't picked up by recursive
1594 // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
1595 if (IsAbbreviation() &&
1596 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName))
1597 aName.CompressWhitespace();
1598
1599 return hasImgAlt ? eNoNameOnPurpose : eNameOK;
1600 }
1601
1602 void
1603 HyperTextAccessible::InvalidateChildren()
1604 {
1605 mOffsets.Clear();
1606
1607 AccessibleWrap::InvalidateChildren();
1608 }
1609
1610 bool
1611 HyperTextAccessible::RemoveChild(Accessible* aAccessible)
1612 {
1613 int32_t childIndex = aAccessible->IndexInParent();
1614 int32_t count = mOffsets.Length() - childIndex;
1615 if (count > 0)
1616 mOffsets.RemoveElementsAt(childIndex, count);
1617
1618 return Accessible::RemoveChild(aAccessible);
1619 }
1620
1621 void
1622 HyperTextAccessible::CacheChildren()
1623 {
1624 // Trailing HTML br element don't play any difference. We don't need to expose
1625 // it to AT (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=899433#c16
1626 // for details).
1627
1628 TreeWalker walker(this, mContent);
1629 Accessible* child = nullptr;
1630 Accessible* lastChild = nullptr;
1631 while ((child = walker.NextChild())) {
1632 if (lastChild)
1633 AppendChild(lastChild);
1634
1635 lastChild = child;
1636 }
1637
1638 if (lastChild) {
1639 if (lastChild->IsHTMLBr())
1640 Document()->UnbindFromDocument(lastChild);
1641 else
1642 AppendChild(lastChild);
1643 }
1644 }
1645
1646 ////////////////////////////////////////////////////////////////////////////////
1647 // HyperTextAccessible public static
1648
1649 nsresult
1650 HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset,
1651 uint32_t* aRenderedOffset) const
1652 {
1653 if (!aFrame) {
1654 // Current frame not rendered -- this can happen if text is set on
1655 // something with display: none
1656 *aRenderedOffset = 0;
1657 return NS_OK;
1658 }
1659
1660 if (IsTextField()) {
1661 *aRenderedOffset = aContentOffset;
1662 return NS_OK;
1663 }
1664
1665 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
1666 "Need text frame for offset conversion");
1667 NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
1668 "Call on primary frame only");
1669
1670 gfxSkipChars skipChars;
1671 gfxSkipCharsIterator iter;
1672 // Only get info up to original offset, we know that will be larger than skipped offset
1673 nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aContentOffset);
1674 NS_ENSURE_SUCCESS(rv, rv);
1675
1676 uint32_t ourRenderedStart = iter.GetSkippedOffset();
1677 int32_t ourContentStart = iter.GetOriginalOffset();
1678
1679 *aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) -
1680 ourRenderedStart;
1681
1682 return NS_OK;
1683 }
1684
1685 nsresult
1686 HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset,
1687 int32_t* aContentOffset) const
1688 {
1689 if (IsTextField()) {
1690 *aContentOffset = aRenderedOffset;
1691 return NS_OK;
1692 }
1693
1694 *aContentOffset = 0;
1695 NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
1696
1697 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
1698 "Need text frame for offset conversion");
1699 NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
1700 "Call on primary frame only");
1701
1702 gfxSkipChars skipChars;
1703 gfxSkipCharsIterator iter;
1704 // We only need info up to skipped offset -- that is what we're converting to original offset
1705 nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aRenderedOffset);
1706 NS_ENSURE_SUCCESS(rv, rv);
1707
1708 uint32_t ourRenderedStart = iter.GetSkippedOffset();
1709 int32_t ourContentStart = iter.GetOriginalOffset();
1710
1711 *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
1712
1713 return NS_OK;
1714 }
1715
1716 ////////////////////////////////////////////////////////////////////////////////
1717 // HyperTextAccessible public
1718
1719 int32_t
1720 HyperTextAccessible::GetChildOffset(uint32_t aChildIndex,
1721 bool aInvalidateAfter) const
1722 {
1723 if (aChildIndex == 0) {
1724 if (aInvalidateAfter)
1725 mOffsets.Clear();
1726
1727 return aChildIndex;
1728 }
1729
1730 int32_t count = mOffsets.Length() - aChildIndex;
1731 if (count > 0) {
1732 if (aInvalidateAfter)
1733 mOffsets.RemoveElementsAt(aChildIndex, count);
1734
1735 return mOffsets[aChildIndex - 1];
1736 }
1737
1738 uint32_t lastOffset = mOffsets.IsEmpty() ?
1739 0 : mOffsets[mOffsets.Length() - 1];
1740
1741 while (mOffsets.Length() < aChildIndex) {
1742 Accessible* child = mChildren[mOffsets.Length()];
1743 lastOffset += nsAccUtils::TextLength(child);
1744 mOffsets.AppendElement(lastOffset);
1745 }
1746
1747 return mOffsets[aChildIndex - 1];
1748 }
1749
1750 int32_t
1751 HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const
1752 {
1753 uint32_t lastOffset = 0;
1754 uint32_t offsetCount = mOffsets.Length();
1755 if (offsetCount > 0) {
1756 lastOffset = mOffsets[offsetCount - 1];
1757 if (aOffset < lastOffset) {
1758 uint32_t low = 0, high = offsetCount;
1759 while (high > low) {
1760 uint32_t mid = (high + low) >> 1;
1761 if (mOffsets[mid] == aOffset)
1762 return mid < offsetCount - 1 ? mid + 1 : mid;
1763
1764 if (mOffsets[mid] < aOffset)
1765 low = mid + 1;
1766 else
1767 high = mid;
1768 }
1769 if (high == offsetCount)
1770 return -1;
1771
1772 return low;
1773 }
1774 }
1775
1776 uint32_t childCount = ChildCount();
1777 while (mOffsets.Length() < childCount) {
1778 Accessible* child = GetChildAt(mOffsets.Length());
1779 lastOffset += nsAccUtils::TextLength(child);
1780 mOffsets.AppendElement(lastOffset);
1781 if (aOffset < lastOffset)
1782 return mOffsets.Length() - 1;
1783 }
1784
1785 if (aOffset == lastOffset)
1786 return mOffsets.Length() - 1;
1787
1788 return -1;
1789 }
1790
1791 ////////////////////////////////////////////////////////////////////////////////
1792 // HyperTextAccessible protected
1793
1794 nsresult
1795 HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
1796 Accessible* aAccessible,
1797 DOMPoint* aPoint)
1798 {
1799 NS_ENSURE_ARG(aAccessible);
1800
1801 if (!aFrame) {
1802 // If the given frame is null then set offset after the DOM node of the
1803 // given accessible.
1804 NS_ASSERTION(!aAccessible->IsDoc(),
1805 "Shouldn't be called on document accessible!");
1806
1807 nsIContent* content = aAccessible->GetContent();
1808 NS_ASSERTION(content, "Shouldn't operate on defunct accessible!");
1809
1810 nsIContent* parent = content->GetParent();
1811
1812 aPoint->idx = parent->IndexOf(content) + 1;
1813 aPoint->node = parent;
1814
1815 } else if (aFrame->GetType() == nsGkAtoms::textFrame) {
1816 nsIContent* content = aFrame->GetContent();
1817 NS_ENSURE_STATE(content);
1818
1819 nsIFrame *primaryFrame = content->GetPrimaryFrame();
1820 nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
1821 NS_ENSURE_SUCCESS(rv, rv);
1822
1823 aPoint->node = content;
1824
1825 } else {
1826 nsIContent* content = aFrame->GetContent();
1827 NS_ENSURE_STATE(content);
1828
1829 nsIContent* parent = content->GetParent();
1830 NS_ENSURE_STATE(parent);
1831
1832 aPoint->idx = parent->IndexOf(content);
1833 aPoint->node = parent;
1834 }
1835
1836 return NS_OK;
1837 }
1838
1839 // HyperTextAccessible
1840 nsresult
1841 HyperTextAccessible::GetSpellTextAttribute(nsINode* aNode,
1842 int32_t aNodeOffset,
1843 int32_t* aHTStartOffset,
1844 int32_t* aHTEndOffset,
1845 nsIPersistentProperties* aAttributes)
1846 {
1847 nsRefPtr<nsFrameSelection> fs = FrameSelection();
1848 if (!fs)
1849 return NS_OK;
1850
1851 dom::Selection* domSel = fs->GetSelection(nsISelectionController::SELECTION_SPELLCHECK);
1852 if (!domSel)
1853 return NS_OK;
1854
1855 int32_t rangeCount = domSel->GetRangeCount();
1856 if (rangeCount <= 0)
1857 return NS_OK;
1858
1859 int32_t startHTOffset = 0, endHTOffset = 0;
1860 for (int32_t idx = 0; idx < rangeCount; idx++) {
1861 nsRange* range = domSel->GetRangeAt(idx);
1862 if (range->Collapsed())
1863 continue;
1864
1865 // See if the point comes after the range in which case we must continue in
1866 // case there is another range after this one.
1867 nsINode* endNode = range->GetEndParent();
1868 int32_t endOffset = range->EndOffset();
1869 if (nsContentUtils::ComparePoints(aNode, aNodeOffset, endNode, endOffset) >= 0)
1870 continue;
1871
1872 // At this point our point is either in this range or before it but after
1873 // the previous range. So we check to see if the range starts before the
1874 // point in which case the point is in the missspelled range, otherwise it
1875 // must be before the range and after the previous one if any.
1876 nsINode* startNode = range->GetStartParent();
1877 int32_t startOffset = range->StartOffset();
1878 if (nsContentUtils::ComparePoints(startNode, startOffset, aNode,
1879 aNodeOffset) <= 0) {
1880 startHTOffset = DOMPointToOffset(startNode, startOffset);
1881
1882 endHTOffset = DOMPointToOffset(endNode, endOffset);
1883
1884 if (startHTOffset > *aHTStartOffset)
1885 *aHTStartOffset = startHTOffset;
1886
1887 if (endHTOffset < *aHTEndOffset)
1888 *aHTEndOffset = endHTOffset;
1889
1890 if (aAttributes) {
1891 nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
1892 NS_LITERAL_STRING("spelling"));
1893 }
1894
1895 return NS_OK;
1896 }
1897
1898 // This range came after the point.
1899 endHTOffset = DOMPointToOffset(startNode, startOffset);
1900
1901 if (idx > 0) {
1902 nsRange* prevRange = domSel->GetRangeAt(idx - 1);
1903 startHTOffset = DOMPointToOffset(prevRange->GetEndParent(),
1904 prevRange->EndOffset());
1905 }
1906
1907 if (startHTOffset > *aHTStartOffset)
1908 *aHTStartOffset = startHTOffset;
1909
1910 if (endHTOffset < *aHTEndOffset)
1911 *aHTEndOffset = endHTOffset;
1912
1913 return NS_OK;
1914 }
1915
1916 // We never found a range that ended after the point, therefore we know that
1917 // the point is not in a range, that we do not need to compute an end offset,
1918 // and that we should use the end offset of the last range to compute the
1919 // start offset of the text attribute range.
1920 nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1);
1921 startHTOffset = DOMPointToOffset(prevRange->GetEndParent(),
1922 prevRange->EndOffset());
1923
1924 if (startHTOffset > *aHTStartOffset)
1925 *aHTStartOffset = startHTOffset;
1926
1927 return NS_OK;
1928 }
1929
1930 bool
1931 HyperTextAccessible::IsTextRole()
1932 {
1933 if (mRoleMapEntry &&
1934 (mRoleMapEntry->role == roles::GRAPHIC ||
1935 mRoleMapEntry->role == roles::IMAGE_MAP ||
1936 mRoleMapEntry->role == roles::SLIDER ||
1937 mRoleMapEntry->role == roles::PROGRESSBAR ||
1938 mRoleMapEntry->role == roles::SEPARATOR))
1939 return false;
1940
1941 return true;
1942 }

mercurial