|
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 } |