/src/mozilla-central/accessible/generic/HyperTextAccessible.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsIEditingSession.h" |
24 | | #include "nsContainerFrame.h" |
25 | | #include "nsFrameSelection.h" |
26 | | #include "nsILineIterator.h" |
27 | | #include "nsIInterfaceRequestorUtils.h" |
28 | | #include "nsPersistentProperties.h" |
29 | | #include "nsIScrollableFrame.h" |
30 | | #include "nsIServiceManager.h" |
31 | | #include "nsITextControlElement.h" |
32 | | #include "nsIMathMLFrame.h" |
33 | | #include "nsRange.h" |
34 | | #include "nsTextFragment.h" |
35 | | #include "mozilla/BinarySearch.h" |
36 | | #include "mozilla/dom/Element.h" |
37 | | #include "mozilla/EventStates.h" |
38 | | #include "mozilla/dom/Selection.h" |
39 | | #include "mozilla/MathAlgorithms.h" |
40 | | #include "mozilla/TextEditor.h" |
41 | | #include "gfxSkipChars.h" |
42 | | #include <algorithm> |
43 | | |
44 | | using namespace mozilla; |
45 | | using namespace mozilla::a11y; |
46 | | |
47 | | //////////////////////////////////////////////////////////////////////////////// |
48 | | // HyperTextAccessible |
49 | | //////////////////////////////////////////////////////////////////////////////// |
50 | | |
51 | | HyperTextAccessible:: |
52 | | HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) : |
53 | | AccessibleWrap(aNode, aDoc) |
54 | 0 | { |
55 | 0 | mType = eHyperTextType; |
56 | 0 | mGenericTypes |= eHyperText; |
57 | 0 | } |
58 | | |
59 | | role |
60 | | HyperTextAccessible::NativeRole() const |
61 | 0 | { |
62 | 0 | a11y::role r = GetAccService()->MarkupRole(mContent); |
63 | 0 | if (r != roles::NOTHING) |
64 | 0 | return r; |
65 | 0 | |
66 | 0 | nsIFrame* frame = GetFrame(); |
67 | 0 | if (frame && frame->IsInlineFrame()) |
68 | 0 | return roles::TEXT; |
69 | 0 | |
70 | 0 | return roles::TEXT_CONTAINER; |
71 | 0 | } |
72 | | |
73 | | uint64_t |
74 | | HyperTextAccessible::NativeState() const |
75 | 0 | { |
76 | 0 | uint64_t states = AccessibleWrap::NativeState(); |
77 | 0 |
|
78 | 0 | if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { |
79 | 0 | states |= states::EDITABLE; |
80 | 0 |
|
81 | 0 | } else if (mContent->IsHTMLElement(nsGkAtoms::article)) { |
82 | 0 | // We want <article> to behave like a document in terms of readonly state. |
83 | 0 | states |= states::READONLY; |
84 | 0 | } |
85 | 0 |
|
86 | 0 | if (HasChildren()) |
87 | 0 | states |= states::SELECTABLE_TEXT; |
88 | 0 |
|
89 | 0 | return states; |
90 | 0 | } |
91 | | |
92 | | nsIntRect |
93 | | HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame, |
94 | | uint32_t aStartRenderedOffset, |
95 | | uint32_t aEndRenderedOffset) |
96 | 0 | { |
97 | 0 | nsPresContext* presContext = mDoc->PresContext(); |
98 | 0 | if (!aFrame->IsTextFrame()) { |
99 | 0 | return aFrame->GetScreenRectInAppUnits(). |
100 | 0 | ToNearestPixels(presContext->AppUnitsPerDevPixel()); |
101 | 0 | } |
102 | 0 | |
103 | 0 | // Substring must be entirely within the same text node. |
104 | 0 | int32_t startContentOffset, endContentOffset; |
105 | 0 | nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset); |
106 | 0 | NS_ENSURE_SUCCESS(rv, nsIntRect()); |
107 | 0 | rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset); |
108 | 0 | NS_ENSURE_SUCCESS(rv, nsIntRect()); |
109 | 0 |
|
110 | 0 | nsIFrame *frame; |
111 | 0 | int32_t startContentOffsetInFrame; |
112 | 0 | // Get the right frame continuation -- not really a child, but a sibling of |
113 | 0 | // the primary frame passed in |
114 | 0 | rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false, |
115 | 0 | &startContentOffsetInFrame, &frame); |
116 | 0 | NS_ENSURE_SUCCESS(rv, nsIntRect()); |
117 | 0 |
|
118 | 0 | nsRect screenRect; |
119 | 0 | while (frame && startContentOffset < endContentOffset) { |
120 | 0 | // Start with this frame's screen rect, which we will shrink based on |
121 | 0 | // the substring we care about within it. We will then add that frame to |
122 | 0 | // the total screenRect we are returning. |
123 | 0 | nsRect frameScreenRect = frame->GetScreenRectInAppUnits(); |
124 | 0 |
|
125 | 0 | // Get the length of the substring in this frame that we want the bounds for |
126 | 0 | int32_t startFrameTextOffset, endFrameTextOffset; |
127 | 0 | frame->GetOffsets(startFrameTextOffset, endFrameTextOffset); |
128 | 0 | int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset; |
129 | 0 | int32_t seekLength = endContentOffset - startContentOffset; |
130 | 0 | int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength); |
131 | 0 |
|
132 | 0 | // Add the point where the string starts to the frameScreenRect |
133 | 0 | nsPoint frameTextStartPoint; |
134 | 0 | rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint); |
135 | 0 | NS_ENSURE_SUCCESS(rv, nsIntRect()); |
136 | 0 |
|
137 | 0 | // Use the point for the end offset to calculate the width |
138 | 0 | nsPoint frameTextEndPoint; |
139 | 0 | rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint); |
140 | 0 | NS_ENSURE_SUCCESS(rv, nsIntRect()); |
141 | 0 |
|
142 | 0 | frameScreenRect.SetRectX(frameScreenRect.X() + std::min(frameTextStartPoint.x, frameTextEndPoint.x), |
143 | 0 | mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x)); |
144 | 0 |
|
145 | 0 | screenRect.UnionRect(frameScreenRect, screenRect); |
146 | 0 |
|
147 | 0 | // Get ready to loop back for next frame continuation |
148 | 0 | startContentOffset += frameSubStringLength; |
149 | 0 | startContentOffsetInFrame = 0; |
150 | 0 | frame = frame->GetNextContinuation(); |
151 | 0 | } |
152 | 0 |
|
153 | 0 | return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel()); |
154 | 0 | } |
155 | | |
156 | | void |
157 | | HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset, |
158 | | nsAString& aText) |
159 | 0 | { |
160 | 0 | aText.Truncate(); |
161 | 0 |
|
162 | 0 | index_t startOffset = ConvertMagicOffset(aStartOffset); |
163 | 0 | index_t endOffset = ConvertMagicOffset(aEndOffset); |
164 | 0 | if (!startOffset.IsValid() || !endOffset.IsValid() || |
165 | 0 | startOffset > endOffset || endOffset > CharacterCount()) { |
166 | 0 | NS_ERROR("Wrong in offset"); |
167 | 0 | return; |
168 | 0 | } |
169 | 0 |
|
170 | 0 | int32_t startChildIdx = GetChildIndexAtOffset(startOffset); |
171 | 0 | if (startChildIdx == -1) |
172 | 0 | return; |
173 | 0 | |
174 | 0 | int32_t endChildIdx = GetChildIndexAtOffset(endOffset); |
175 | 0 | if (endChildIdx == -1) |
176 | 0 | return; |
177 | 0 | |
178 | 0 | if (startChildIdx == endChildIdx) { |
179 | 0 | int32_t childOffset = GetChildOffset(startChildIdx); |
180 | 0 | if (childOffset == -1) |
181 | 0 | return; |
182 | 0 | |
183 | 0 | Accessible* child = GetChildAt(startChildIdx); |
184 | 0 | child->AppendTextTo(aText, startOffset - childOffset, |
185 | 0 | endOffset - startOffset); |
186 | 0 | return; |
187 | 0 | } |
188 | 0 | |
189 | 0 | int32_t startChildOffset = GetChildOffset(startChildIdx); |
190 | 0 | if (startChildOffset == -1) |
191 | 0 | return; |
192 | 0 | |
193 | 0 | Accessible* startChild = GetChildAt(startChildIdx); |
194 | 0 | startChild->AppendTextTo(aText, startOffset - startChildOffset); |
195 | 0 |
|
196 | 0 | for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) { |
197 | 0 | Accessible* child = GetChildAt(childIdx); |
198 | 0 | child->AppendTextTo(aText); |
199 | 0 | } |
200 | 0 |
|
201 | 0 | int32_t endChildOffset = GetChildOffset(endChildIdx); |
202 | 0 | if (endChildOffset == -1) |
203 | 0 | return; |
204 | 0 | |
205 | 0 | Accessible* endChild = GetChildAt(endChildIdx); |
206 | 0 | endChild->AppendTextTo(aText, 0, endOffset - endChildOffset); |
207 | 0 | } |
208 | | |
209 | | uint32_t |
210 | | HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset, |
211 | | bool aIsEndOffset) const |
212 | 0 | { |
213 | 0 | if (!aNode) |
214 | 0 | return 0; |
215 | 0 | |
216 | 0 | uint32_t offset = 0; |
217 | 0 | nsINode* findNode = nullptr; |
218 | 0 |
|
219 | 0 | if (aNodeOffset == -1) { |
220 | 0 | findNode = aNode; |
221 | 0 |
|
222 | 0 | } else if (aNode->IsText()) { |
223 | 0 | // For text nodes, aNodeOffset comes in as a character offset |
224 | 0 | // Text offset will be added at the end, if we find the offset in this hypertext |
225 | 0 | // We want the "skipped" offset into the text (rendered text without the extra whitespace) |
226 | 0 | nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); |
227 | 0 | NS_ENSURE_TRUE(frame, 0); |
228 | 0 |
|
229 | 0 | nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset); |
230 | 0 | NS_ENSURE_SUCCESS(rv, 0); |
231 | 0 |
|
232 | 0 | findNode = aNode; |
233 | 0 |
|
234 | 0 | } else { |
235 | 0 | // findNode could be null if aNodeOffset == # of child nodes, which means |
236 | 0 | // one of two things: |
237 | 0 | // 1) there are no children, and the passed-in node is not mContent -- use |
238 | 0 | // parentContent for the node to find |
239 | 0 | // 2) there are no children and the passed-in node is mContent, which means |
240 | 0 | // we're an empty nsIAccessibleText |
241 | 0 | // 3) there are children and we're at the end of the children |
242 | 0 |
|
243 | 0 | findNode = aNode->GetChildAt_Deprecated(aNodeOffset); |
244 | 0 | if (!findNode) { |
245 | 0 | if (aNodeOffset == 0) { |
246 | 0 | if (aNode == GetNode()) { |
247 | 0 | // Case #1: this accessible has no children and thus has empty text, |
248 | 0 | // we can only be at hypertext offset 0. |
249 | 0 | return 0; |
250 | 0 | } |
251 | 0 | |
252 | 0 | // Case #2: there are no children, we're at this node. |
253 | 0 | findNode = aNode; |
254 | 0 | } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) { |
255 | 0 | // Case #3: we're after the last child, get next node to this one. |
256 | 0 | for (nsINode* tmpNode = aNode; |
257 | 0 | !findNode && tmpNode && tmpNode != mContent; |
258 | 0 | tmpNode = tmpNode->GetParent()) { |
259 | 0 | findNode = tmpNode->GetNextSibling(); |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | 0 | } |
264 | 0 |
|
265 | 0 | // Get accessible for this findNode, or if that node isn't accessible, use the |
266 | 0 | // accessible for the next DOM node which has one (based on forward depth first search) |
267 | 0 | Accessible* descendant = nullptr; |
268 | 0 | if (findNode) { |
269 | 0 | nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode)); |
270 | 0 | if (findContent && findContent->IsHTMLElement(nsGkAtoms::br) && |
271 | 0 | findContent->AsElement()->AttrValueIs(kNameSpaceID_None, |
272 | 0 | nsGkAtoms::mozeditorbogusnode, |
273 | 0 | nsGkAtoms::_true, |
274 | 0 | eIgnoreCase)) { |
275 | 0 | // This <br> is the hacky "bogus node" used when there is no text in a control |
276 | 0 | return 0; |
277 | 0 | } |
278 | 0 | |
279 | 0 | descendant = mDoc->GetAccessible(findNode); |
280 | 0 | if (!descendant && findNode->IsContent()) { |
281 | 0 | Accessible* container = mDoc->GetContainerAccessible(findNode); |
282 | 0 | if (container) { |
283 | 0 | TreeWalker walker(container, findNode->AsContent(), |
284 | 0 | TreeWalker::eWalkContextTree); |
285 | 0 | descendant = walker.Next(); |
286 | 0 | if (!descendant) |
287 | 0 | descendant = container; |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | 0 |
|
292 | 0 | return TransformOffset(descendant, offset, aIsEndOffset); |
293 | 0 | } |
294 | | |
295 | | uint32_t |
296 | | HyperTextAccessible::TransformOffset(Accessible* aDescendant, |
297 | | uint32_t aOffset, bool aIsEndOffset) const |
298 | 0 | { |
299 | 0 | // From the descendant, go up and get the immediate child of this hypertext. |
300 | 0 | uint32_t offset = aOffset; |
301 | 0 | Accessible* descendant = aDescendant; |
302 | 0 | while (descendant) { |
303 | 0 | Accessible* parent = descendant->Parent(); |
304 | 0 | if (parent == this) |
305 | 0 | return GetChildOffset(descendant) + offset; |
306 | 0 | |
307 | 0 | // This offset no longer applies because the passed-in text object is not |
308 | 0 | // a child of the hypertext. This happens when there are nested hypertexts, |
309 | 0 | // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset |
310 | 0 | // to make it relative the hypertext. |
311 | 0 | // If the end offset is not supposed to be inclusive and the original point |
312 | 0 | // is not at 0 offset then the returned offset should be after an embedded |
313 | 0 | // character the original point belongs to. |
314 | 0 | if (aIsEndOffset) |
315 | 0 | offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0; |
316 | 0 | else |
317 | 0 | offset = 0; |
318 | 0 |
|
319 | 0 | descendant = parent; |
320 | 0 | } |
321 | 0 |
|
322 | 0 | // If the given a11y point cannot be mapped into offset relative this hypertext |
323 | 0 | // offset then return length as fallback value. |
324 | 0 | return CharacterCount(); |
325 | 0 | } |
326 | | |
327 | | /** |
328 | | * GetElementAsContentOf() returns a content representing an element which is |
329 | | * or includes aNode. |
330 | | * |
331 | | * XXX This method is enough to retrieve ::before or ::after pseudo element. |
332 | | * So, if you want to use this for other purpose, you might need to check |
333 | | * ancestors too. |
334 | | */ |
335 | | static nsIContent* GetElementAsContentOf(nsINode* aNode) |
336 | 0 | { |
337 | 0 | if (Element* element = Element::FromNode(aNode)) { |
338 | 0 | return element; |
339 | 0 | } |
340 | 0 | return aNode->GetParentElement(); |
341 | 0 | } |
342 | | |
343 | | bool |
344 | | HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset, |
345 | | nsRange* aRange) |
346 | 0 | { |
347 | 0 | DOMPoint startPoint = OffsetToDOMPoint(aStartOffset); |
348 | 0 | if (!startPoint.node) |
349 | 0 | return false; |
350 | 0 | |
351 | 0 | // HyperTextAccessible manages pseudo elements generated by ::before or |
352 | 0 | // ::after. However, contents of them are not in the DOM tree normally. |
353 | 0 | // Therefore, they are not selectable and editable. So, when this creates |
354 | 0 | // a DOM range, it should not start from nor end in any pseudo contents. |
355 | 0 | |
356 | 0 | nsIContent* container = GetElementAsContentOf(startPoint.node); |
357 | 0 | DOMPoint startPointForDOMRange = |
358 | 0 | ClosestNotGeneratedDOMPoint(startPoint, container); |
359 | 0 | aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx); |
360 | 0 |
|
361 | 0 | // If the caller wants collapsed range, let's collapse the range to its start. |
362 | 0 | if (aStartOffset == aEndOffset) { |
363 | 0 | aRange->Collapse(true); |
364 | 0 | return true; |
365 | 0 | } |
366 | 0 | |
367 | 0 | DOMPoint endPoint = OffsetToDOMPoint(aEndOffset); |
368 | 0 | if (!endPoint.node) |
369 | 0 | return false; |
370 | 0 | |
371 | 0 | if (startPoint.node != endPoint.node) { |
372 | 0 | container = GetElementAsContentOf(endPoint.node); |
373 | 0 | } |
374 | 0 |
|
375 | 0 | DOMPoint endPointForDOMRange = |
376 | 0 | ClosestNotGeneratedDOMPoint(endPoint, container); |
377 | 0 | aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx); |
378 | 0 | return true; |
379 | 0 | } |
380 | | |
381 | | DOMPoint |
382 | | HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset) |
383 | 0 | { |
384 | 0 | // 0 offset is valid even if no children. In this case the associated editor |
385 | 0 | // is empty so return a DOM point for editor root element. |
386 | 0 | if (aOffset == 0) { |
387 | 0 | RefPtr<TextEditor> textEditor = GetEditor(); |
388 | 0 | if (textEditor) { |
389 | 0 | if (textEditor->IsEmpty()) { |
390 | 0 | return DOMPoint(textEditor->GetRoot(), 0); |
391 | 0 | } |
392 | 0 | } |
393 | 0 | } |
394 | 0 | |
395 | 0 | int32_t childIdx = GetChildIndexAtOffset(aOffset); |
396 | 0 | if (childIdx == -1) |
397 | 0 | return DOMPoint(); |
398 | 0 | |
399 | 0 | Accessible* child = GetChildAt(childIdx); |
400 | 0 | int32_t innerOffset = aOffset - GetChildOffset(childIdx); |
401 | 0 |
|
402 | 0 | // A text leaf case. |
403 | 0 | if (child->IsTextLeaf()) { |
404 | 0 | // The point is inside the text node. This is always true for any text leaf |
405 | 0 | // except a last child one. See assertion below. |
406 | 0 | if (aOffset < GetChildOffset(childIdx + 1)) { |
407 | 0 | nsIContent* content = child->GetContent(); |
408 | 0 | int32_t idx = 0; |
409 | 0 | if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(), |
410 | 0 | innerOffset, &idx))) |
411 | 0 | return DOMPoint(); |
412 | 0 | |
413 | 0 | return DOMPoint(content, idx); |
414 | 0 | } |
415 | 0 | |
416 | 0 | // Set the DOM point right after the text node. |
417 | 0 | MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount()); |
418 | 0 | innerOffset = 1; |
419 | 0 | } |
420 | 0 |
|
421 | 0 | // Case of embedded object. The point is either before or after the element. |
422 | 0 | NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!"); |
423 | 0 | nsINode* node = child->GetNode(); |
424 | 0 | nsINode* parentNode = node->GetParentNode(); |
425 | 0 | return parentNode ? |
426 | 0 | DOMPoint(parentNode, parentNode->ComputeIndexOf(node) + innerOffset) : |
427 | 0 | DOMPoint(); |
428 | 0 | } |
429 | | |
430 | | DOMPoint |
431 | | HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint, |
432 | | nsIContent* aElementContent) |
433 | 0 | { |
434 | 0 | MOZ_ASSERT(aDOMPoint.node, "The node must not be null"); |
435 | 0 |
|
436 | 0 | // ::before pseudo element |
437 | 0 | if (aElementContent && |
438 | 0 | aElementContent->IsGeneratedContentContainerForBefore()) { |
439 | 0 | MOZ_ASSERT(aElementContent->GetParent(), |
440 | 0 | "::before must have parent element"); |
441 | 0 | // The first child of its parent (i.e., immediately after the ::before) is |
442 | 0 | // good point for a DOM range. |
443 | 0 | return DOMPoint(aElementContent->GetParent(), 0); |
444 | 0 | } |
445 | 0 |
|
446 | 0 | // ::after pseudo element |
447 | 0 | if (aElementContent && |
448 | 0 | aElementContent->IsGeneratedContentContainerForAfter()) { |
449 | 0 | MOZ_ASSERT(aElementContent->GetParent(), |
450 | 0 | "::after must have parent element"); |
451 | 0 | // The end of its parent (i.e., immediately before the ::after) is good |
452 | 0 | // point for a DOM range. |
453 | 0 | return DOMPoint(aElementContent->GetParent(), |
454 | 0 | aElementContent->GetParent()->GetChildCount()); |
455 | 0 | } |
456 | 0 |
|
457 | 0 | return aDOMPoint; |
458 | 0 | } |
459 | | |
460 | | uint32_t |
461 | | HyperTextAccessible::FindOffset(uint32_t aOffset, nsDirection aDirection, |
462 | | nsSelectionAmount aAmount, |
463 | | EWordMovementType aWordMovementType) |
464 | 0 | { |
465 | 0 | NS_ASSERTION(aDirection == eDirPrevious || aAmount != eSelectBeginLine, |
466 | 0 | "eSelectBeginLine should only be used with eDirPrevious"); |
467 | 0 |
|
468 | 0 | // Find a leaf accessible frame to start with. PeekOffset wants this. |
469 | 0 | HyperTextAccessible* text = this; |
470 | 0 | Accessible* child = nullptr; |
471 | 0 | int32_t innerOffset = aOffset; |
472 | 0 |
|
473 | 0 | do { |
474 | 0 | int32_t childIdx = text->GetChildIndexAtOffset(innerOffset); |
475 | 0 |
|
476 | 0 | // We can have an empty text leaf as our only child. Since empty text |
477 | 0 | // leaves are not accessible we then have no children, but 0 is a valid |
478 | 0 | // innerOffset. |
479 | 0 | if (childIdx == -1) { |
480 | 0 | NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?"); |
481 | 0 | return DOMPointToOffset(text->GetNode(), 0, aDirection == eDirNext); |
482 | 0 | } |
483 | 0 |
|
484 | 0 | child = text->GetChildAt(childIdx); |
485 | 0 |
|
486 | 0 | // HTML list items may need special processing because PeekOffset doesn't |
487 | 0 | // work with list bullets. |
488 | 0 | if (text->IsHTMLListItem()) { |
489 | 0 | HTMLLIAccessible* li = text->AsHTMLListItem(); |
490 | 0 | if (child == li->Bullet()) { |
491 | 0 | // XXX: the logic is broken for multichar bullets in moving by |
492 | 0 | // char/cluster/word cases. |
493 | 0 | if (text != this) { |
494 | 0 | return aDirection == eDirPrevious ? |
495 | 0 | TransformOffset(text, 0, false) : |
496 | 0 | TransformOffset(text, 1, true); |
497 | 0 | } |
498 | 0 | if (aDirection == eDirPrevious) |
499 | 0 | return 0; |
500 | 0 | |
501 | 0 | uint32_t nextOffset = GetChildOffset(1); |
502 | 0 | if (nextOffset == 0) |
503 | 0 | return 0; |
504 | 0 | |
505 | 0 | switch (aAmount) { |
506 | 0 | case eSelectLine: |
507 | 0 | case eSelectEndLine: |
508 | 0 | // Ask a text leaf next (if not empty) to the bullet for an offset |
509 | 0 | // since list item may be multiline. |
510 | 0 | return nextOffset < CharacterCount() ? |
511 | 0 | FindOffset(nextOffset, aDirection, aAmount, aWordMovementType) : |
512 | 0 | nextOffset; |
513 | 0 |
|
514 | 0 | default: |
515 | 0 | return nextOffset; |
516 | 0 | } |
517 | 0 | } |
518 | 0 | } |
519 | 0 | |
520 | 0 | innerOffset -= text->GetChildOffset(childIdx); |
521 | 0 |
|
522 | 0 | text = child->AsHyperText(); |
523 | 0 | } while (text); |
524 | 0 |
|
525 | 0 | nsIFrame* childFrame = child->GetFrame(); |
526 | 0 | if (!childFrame) { |
527 | 0 | NS_ERROR("No child frame"); |
528 | 0 | return 0; |
529 | 0 | } |
530 | 0 |
|
531 | 0 | int32_t innerContentOffset = innerOffset; |
532 | 0 | if (child->IsTextLeaf()) { |
533 | 0 | NS_ASSERTION(childFrame->IsTextFrame(), "Wrong frame!"); |
534 | 0 | RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset); |
535 | 0 | } |
536 | 0 |
|
537 | 0 | nsIFrame* frameAtOffset = childFrame; |
538 | 0 | int32_t unusedOffsetInFrame = 0; |
539 | 0 | childFrame->GetChildFrameContainingOffset(innerContentOffset, true, |
540 | 0 | &unusedOffsetInFrame, |
541 | 0 | &frameAtOffset); |
542 | 0 |
|
543 | 0 | const bool kIsJumpLinesOk = true; // okay to jump lines |
544 | 0 | const bool kIsScrollViewAStop = false; // do not stop at scroll views |
545 | 0 | const bool kIsKeyboardSelect = true; // is keyboard selection |
546 | 0 | const bool kIsVisualBidi = false; // use visual order for bidi text |
547 | 0 | nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset, |
548 | 0 | nsPoint(0, 0), kIsJumpLinesOk, kIsScrollViewAStop, |
549 | 0 | kIsKeyboardSelect, kIsVisualBidi, |
550 | 0 | false, aWordMovementType); |
551 | 0 | nsresult rv = frameAtOffset->PeekOffset(&pos); |
552 | 0 |
|
553 | 0 | // PeekOffset fails on last/first lines of the text in certain cases. |
554 | 0 | if (NS_FAILED(rv) && aAmount == eSelectLine) { |
555 | 0 | pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine; |
556 | 0 | frameAtOffset->PeekOffset(&pos); |
557 | 0 | } |
558 | 0 | if (!pos.mResultContent) { |
559 | 0 | NS_ERROR("No result content!"); |
560 | 0 | return 0; |
561 | 0 | } |
562 | 0 |
|
563 | 0 | // Turn the resulting DOM point into an offset. |
564 | 0 | uint32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent, |
565 | 0 | pos.mContentOffset, |
566 | 0 | aDirection == eDirNext); |
567 | 0 |
|
568 | 0 | if (aDirection == eDirPrevious) { |
569 | 0 | // If we reached the end during search, this means we didn't find the DOM point |
570 | 0 | // and we're actually at the start of the paragraph |
571 | 0 | if (hyperTextOffset == CharacterCount()) |
572 | 0 | return 0; |
573 | 0 | |
574 | 0 | // PeekOffset stops right before bullet so return 0 to workaround it. |
575 | 0 | if (IsHTMLListItem() && aAmount == eSelectBeginLine && |
576 | 0 | hyperTextOffset > 0) { |
577 | 0 | Accessible* prevOffsetChild = GetChildAtOffset(hyperTextOffset - 1); |
578 | 0 | if (prevOffsetChild == AsHTMLListItem()->Bullet()) |
579 | 0 | return 0; |
580 | 0 | } |
581 | 0 | } |
582 | 0 | |
583 | 0 | return hyperTextOffset; |
584 | 0 | } |
585 | | |
586 | | uint32_t |
587 | | HyperTextAccessible::FindLineBoundary(uint32_t aOffset, |
588 | | EWhichLineBoundary aWhichLineBoundary) |
589 | 0 | { |
590 | 0 | // Note: empty last line doesn't have own frame (a previous line contains '\n' |
591 | 0 | // character instead) thus when it makes a difference we need to process this |
592 | 0 | // case separately (otherwise operations are performed on previous line). |
593 | 0 | switch (aWhichLineBoundary) { |
594 | 0 | case ePrevLineBegin: { |
595 | 0 | // Fetch a previous line and move to its start (as arrow up and home keys |
596 | 0 | // were pressed). |
597 | 0 | if (IsEmptyLastLineOffset(aOffset)) |
598 | 0 | return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); |
599 | 0 | |
600 | 0 | uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); |
601 | 0 | return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); |
602 | 0 | } |
603 | 0 |
|
604 | 0 | case ePrevLineEnd: { |
605 | 0 | if (IsEmptyLastLineOffset(aOffset)) |
606 | 0 | return aOffset - 1; |
607 | 0 | |
608 | 0 | // If offset is at first line then return 0 (first line start). |
609 | 0 | uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine); |
610 | 0 | if (tmpOffset == 0) |
611 | 0 | return 0; |
612 | 0 | |
613 | 0 | // Otherwise move to end of previous line (as arrow up and end keys were |
614 | 0 | // pressed). |
615 | 0 | tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); |
616 | 0 | return FindOffset(tmpOffset, eDirNext, eSelectEndLine); |
617 | 0 | } |
618 | 0 |
|
619 | 0 | case eThisLineBegin: |
620 | 0 | if (IsEmptyLastLineOffset(aOffset)) |
621 | 0 | return aOffset; |
622 | 0 | |
623 | 0 | // Move to begin of the current line (as home key was pressed). |
624 | 0 | return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); |
625 | 0 |
|
626 | 0 | case eThisLineEnd: |
627 | 0 | if (IsEmptyLastLineOffset(aOffset)) |
628 | 0 | return aOffset; |
629 | 0 | |
630 | 0 | // Move to end of the current line (as end key was pressed). |
631 | 0 | return FindOffset(aOffset, eDirNext, eSelectEndLine); |
632 | 0 |
|
633 | 0 | case eNextLineBegin: { |
634 | 0 | if (IsEmptyLastLineOffset(aOffset)) |
635 | 0 | return aOffset; |
636 | 0 | |
637 | 0 | // Move to begin of the next line if any (arrow down and home keys), |
638 | 0 | // otherwise end of the current line (arrow down only). |
639 | 0 | uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); |
640 | 0 | if (tmpOffset == CharacterCount()) |
641 | 0 | return tmpOffset; |
642 | 0 | |
643 | 0 | return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); |
644 | 0 | } |
645 | 0 |
|
646 | 0 | case eNextLineEnd: { |
647 | 0 | if (IsEmptyLastLineOffset(aOffset)) |
648 | 0 | return aOffset; |
649 | 0 | |
650 | 0 | // Move to next line end (as down arrow and end key were pressed). |
651 | 0 | uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); |
652 | 0 | if (tmpOffset == CharacterCount()) |
653 | 0 | return tmpOffset; |
654 | 0 | |
655 | 0 | return FindOffset(tmpOffset, eDirNext, eSelectEndLine); |
656 | 0 | } |
657 | 0 | } |
658 | 0 | |
659 | 0 | return 0; |
660 | 0 | } |
661 | | |
662 | | void |
663 | | HyperTextAccessible::TextBeforeOffset(int32_t aOffset, |
664 | | AccessibleTextBoundary aBoundaryType, |
665 | | int32_t* aStartOffset, int32_t* aEndOffset, |
666 | | nsAString& aText) |
667 | 0 | { |
668 | 0 | *aStartOffset = *aEndOffset = 0; |
669 | 0 | aText.Truncate(); |
670 | 0 |
|
671 | 0 | index_t convertedOffset = ConvertMagicOffset(aOffset); |
672 | 0 | if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) { |
673 | 0 | NS_ERROR("Wrong in offset!"); |
674 | 0 | return; |
675 | 0 | } |
676 | 0 |
|
677 | 0 | uint32_t adjustedOffset = convertedOffset; |
678 | 0 | if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) |
679 | 0 | adjustedOffset = AdjustCaretOffset(adjustedOffset); |
680 | 0 |
|
681 | 0 | switch (aBoundaryType) { |
682 | 0 | case nsIAccessibleText::BOUNDARY_CHAR: |
683 | 0 | if (convertedOffset != 0) |
684 | 0 | CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset); |
685 | 0 | break; |
686 | 0 |
|
687 | 0 | case nsIAccessibleText::BOUNDARY_WORD_START: { |
688 | 0 | // If the offset is a word start (except text length offset) then move |
689 | 0 | // backward to find a start offset (end offset is the given offset). |
690 | 0 | // Otherwise move backward twice to find both start and end offsets. |
691 | 0 | if (adjustedOffset == CharacterCount()) { |
692 | 0 | *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); |
693 | 0 | *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); |
694 | 0 | } else { |
695 | 0 | *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); |
696 | 0 | *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); |
697 | 0 | if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) { |
698 | 0 | *aEndOffset = *aStartOffset; |
699 | 0 | *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); |
700 | 0 | } |
701 | 0 | } |
702 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
703 | 0 | break; |
704 | 0 | } |
705 | 0 |
|
706 | 0 | case nsIAccessibleText::BOUNDARY_WORD_END: { |
707 | 0 | // Move word backward twice to find start and end offsets. |
708 | 0 | *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord); |
709 | 0 | *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); |
710 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
711 | 0 | break; |
712 | 0 | } |
713 | 0 |
|
714 | 0 | case nsIAccessibleText::BOUNDARY_LINE_START: |
715 | 0 | *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin); |
716 | 0 | *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); |
717 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
718 | 0 | break; |
719 | 0 |
|
720 | 0 | case nsIAccessibleText::BOUNDARY_LINE_END: { |
721 | 0 | *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); |
722 | 0 | int32_t tmpOffset = *aEndOffset; |
723 | 0 | // Adjust offset if line is wrapped. |
724 | 0 | if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset)) |
725 | 0 | tmpOffset--; |
726 | 0 |
|
727 | 0 | *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd); |
728 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
729 | 0 | break; |
730 | 0 | } |
731 | 0 | } |
732 | 0 | } |
733 | | |
734 | | void |
735 | | HyperTextAccessible::TextAtOffset(int32_t aOffset, |
736 | | AccessibleTextBoundary aBoundaryType, |
737 | | int32_t* aStartOffset, int32_t* aEndOffset, |
738 | | nsAString& aText) |
739 | 0 | { |
740 | 0 | *aStartOffset = *aEndOffset = 0; |
741 | 0 | aText.Truncate(); |
742 | 0 |
|
743 | 0 | uint32_t adjustedOffset = ConvertMagicOffset(aOffset); |
744 | 0 | if (adjustedOffset == std::numeric_limits<uint32_t>::max()) { |
745 | 0 | NS_ERROR("Wrong given offset!"); |
746 | 0 | return; |
747 | 0 | } |
748 | 0 |
|
749 | 0 | switch (aBoundaryType) { |
750 | 0 | case nsIAccessibleText::BOUNDARY_CHAR: |
751 | 0 | // Return no char if caret is at the end of wrapped line (case of no line |
752 | 0 | // end character). Returning a next line char is confusing for AT. |
753 | 0 | if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine()) |
754 | 0 | *aStartOffset = *aEndOffset = adjustedOffset; |
755 | 0 | else |
756 | 0 | CharAt(adjustedOffset, aText, aStartOffset, aEndOffset); |
757 | 0 | break; |
758 | 0 |
|
759 | 0 | case nsIAccessibleText::BOUNDARY_WORD_START: |
760 | 0 | if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) |
761 | 0 | adjustedOffset = AdjustCaretOffset(adjustedOffset); |
762 | 0 |
|
763 | 0 | *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); |
764 | 0 | *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); |
765 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
766 | 0 | break; |
767 | 0 |
|
768 | 0 | case nsIAccessibleText::BOUNDARY_WORD_END: |
769 | 0 | // Ignore the spec and follow what WebKitGtk does because Orca expects it, |
770 | 0 | // i.e. return a next word at word end offset of the current word |
771 | 0 | // (WebKitGtk behavior) instead the current word (AKT spec). |
772 | 0 | *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord); |
773 | 0 | *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); |
774 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
775 | 0 | break; |
776 | 0 |
|
777 | 0 | case nsIAccessibleText::BOUNDARY_LINE_START: |
778 | 0 | if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) |
779 | 0 | adjustedOffset = AdjustCaretOffset(adjustedOffset); |
780 | 0 |
|
781 | 0 | *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); |
782 | 0 | *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); |
783 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
784 | 0 | break; |
785 | 0 |
|
786 | 0 | case nsIAccessibleText::BOUNDARY_LINE_END: |
787 | 0 | if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) |
788 | 0 | adjustedOffset = AdjustCaretOffset(adjustedOffset); |
789 | 0 |
|
790 | 0 | // In contrast to word end boundary we follow the spec here. |
791 | 0 | *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); |
792 | 0 | *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); |
793 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
794 | 0 | break; |
795 | 0 | } |
796 | 0 | } |
797 | | |
798 | | void |
799 | | HyperTextAccessible::TextAfterOffset(int32_t aOffset, |
800 | | AccessibleTextBoundary aBoundaryType, |
801 | | int32_t* aStartOffset, int32_t* aEndOffset, |
802 | | nsAString& aText) |
803 | 0 | { |
804 | 0 | *aStartOffset = *aEndOffset = 0; |
805 | 0 | aText.Truncate(); |
806 | 0 |
|
807 | 0 | index_t convertedOffset = ConvertMagicOffset(aOffset); |
808 | 0 | if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) { |
809 | 0 | NS_ERROR("Wrong in offset!"); |
810 | 0 | return; |
811 | 0 | } |
812 | 0 |
|
813 | 0 | uint32_t adjustedOffset = convertedOffset; |
814 | 0 | if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) |
815 | 0 | adjustedOffset = AdjustCaretOffset(adjustedOffset); |
816 | 0 |
|
817 | 0 | switch (aBoundaryType) { |
818 | 0 | case nsIAccessibleText::BOUNDARY_CHAR: |
819 | 0 | // If caret is at the end of wrapped line (case of no line end character) |
820 | 0 | // then char after the offset is a first char at next line. |
821 | 0 | if (adjustedOffset >= CharacterCount()) |
822 | 0 | *aStartOffset = *aEndOffset = CharacterCount(); |
823 | 0 | else |
824 | 0 | CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset); |
825 | 0 | break; |
826 | 0 |
|
827 | 0 | case nsIAccessibleText::BOUNDARY_WORD_START: |
828 | 0 | // Move word forward twice to find start and end offsets. |
829 | 0 | *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); |
830 | 0 | *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); |
831 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
832 | 0 | break; |
833 | 0 |
|
834 | 0 | case nsIAccessibleText::BOUNDARY_WORD_END: |
835 | 0 | // If the offset is a word end (except 0 offset) then move forward to find |
836 | 0 | // end offset (start offset is the given offset). Otherwise move forward |
837 | 0 | // twice to find both start and end offsets. |
838 | 0 | if (convertedOffset == 0) { |
839 | 0 | *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); |
840 | 0 | *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); |
841 | 0 | } else { |
842 | 0 | *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); |
843 | 0 | *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); |
844 | 0 | if (*aStartOffset != static_cast<int32_t>(convertedOffset)) { |
845 | 0 | *aStartOffset = *aEndOffset; |
846 | 0 | *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); |
847 | 0 | } |
848 | 0 | } |
849 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
850 | 0 | break; |
851 | 0 |
|
852 | 0 | case nsIAccessibleText::BOUNDARY_LINE_START: |
853 | 0 | *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); |
854 | 0 | *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin); |
855 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
856 | 0 | break; |
857 | 0 |
|
858 | 0 | case nsIAccessibleText::BOUNDARY_LINE_END: |
859 | 0 | *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); |
860 | 0 | *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd); |
861 | 0 | TextSubstring(*aStartOffset, *aEndOffset, aText); |
862 | 0 | break; |
863 | 0 | } |
864 | 0 | } |
865 | | |
866 | | already_AddRefed<nsIPersistentProperties> |
867 | | HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset, |
868 | | int32_t* aStartOffset, |
869 | | int32_t* aEndOffset) |
870 | 0 | { |
871 | 0 | // 1. Get each attribute and its ranges one after another. |
872 | 0 | // 2. As we get each new attribute, we pass the current start and end offsets |
873 | 0 | // as in/out parameters. In other words, as attributes are collected, |
874 | 0 | // the attribute range itself can only stay the same or get smaller. |
875 | 0 |
|
876 | 0 | *aStartOffset = *aEndOffset = 0; |
877 | 0 | index_t offset = ConvertMagicOffset(aOffset); |
878 | 0 | if (!offset.IsValid() || offset > CharacterCount()) { |
879 | 0 | NS_ERROR("Wrong in offset!"); |
880 | 0 | return nullptr; |
881 | 0 | } |
882 | 0 |
|
883 | 0 | RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties(); |
884 | 0 |
|
885 | 0 | Accessible* accAtOffset = GetChildAtOffset(offset); |
886 | 0 | if (!accAtOffset) { |
887 | 0 | // Offset 0 is correct offset when accessible has empty text. Include |
888 | 0 | // default attributes if they were requested, otherwise return empty set. |
889 | 0 | if (offset == 0) { |
890 | 0 | if (aIncludeDefAttrs) { |
891 | 0 | TextAttrsMgr textAttrsMgr(this); |
892 | 0 | textAttrsMgr.GetAttributes(attributes); |
893 | 0 | } |
894 | 0 | return attributes.forget(); |
895 | 0 | } |
896 | 0 | return nullptr; |
897 | 0 | } |
898 | 0 | |
899 | 0 | int32_t accAtOffsetIdx = accAtOffset->IndexInParent(); |
900 | 0 | uint32_t startOffset = GetChildOffset(accAtOffsetIdx); |
901 | 0 | uint32_t endOffset = GetChildOffset(accAtOffsetIdx + 1); |
902 | 0 | int32_t offsetInAcc = offset - startOffset; |
903 | 0 |
|
904 | 0 | TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset, |
905 | 0 | accAtOffsetIdx); |
906 | 0 | textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset); |
907 | 0 |
|
908 | 0 | // Compute spelling attributes on text accessible only. |
909 | 0 | nsIFrame *offsetFrame = accAtOffset->GetFrame(); |
910 | 0 | if (offsetFrame && offsetFrame->IsTextFrame()) { |
911 | 0 | int32_t nodeOffset = 0; |
912 | 0 | RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset); |
913 | 0 |
|
914 | 0 | // Set 'misspelled' text attribute. |
915 | 0 | GetSpellTextAttr(accAtOffset->GetNode(), nodeOffset, |
916 | 0 | &startOffset, &endOffset, attributes); |
917 | 0 | } |
918 | 0 |
|
919 | 0 | *aStartOffset = startOffset; |
920 | 0 | *aEndOffset = endOffset; |
921 | 0 | return attributes.forget(); |
922 | 0 | } |
923 | | |
924 | | already_AddRefed<nsIPersistentProperties> |
925 | | HyperTextAccessible::DefaultTextAttributes() |
926 | 0 | { |
927 | 0 | RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties(); |
928 | 0 |
|
929 | 0 | TextAttrsMgr textAttrsMgr(this); |
930 | 0 | textAttrsMgr.GetAttributes(attributes); |
931 | 0 | return attributes.forget(); |
932 | 0 | } |
933 | | |
934 | | int32_t |
935 | | HyperTextAccessible::GetLevelInternal() |
936 | 0 | { |
937 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::h1)) |
938 | 0 | return 1; |
939 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::h2)) |
940 | 0 | return 2; |
941 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::h3)) |
942 | 0 | return 3; |
943 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::h4)) |
944 | 0 | return 4; |
945 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::h5)) |
946 | 0 | return 5; |
947 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::h6)) |
948 | 0 | return 6; |
949 | 0 | |
950 | 0 | return AccessibleWrap::GetLevelInternal(); |
951 | 0 | } |
952 | | |
953 | | void |
954 | | HyperTextAccessible::SetMathMLXMLRoles(nsIPersistentProperties* aAttributes) |
955 | 0 | { |
956 | 0 | // Add MathML xmlroles based on the position inside the parent. |
957 | 0 | Accessible* parent = Parent(); |
958 | 0 | if (parent) { |
959 | 0 | switch (parent->Role()) { |
960 | 0 | case roles::MATHML_CELL: |
961 | 0 | case roles::MATHML_ENCLOSED: |
962 | 0 | case roles::MATHML_ERROR: |
963 | 0 | case roles::MATHML_MATH: |
964 | 0 | case roles::MATHML_ROW: |
965 | 0 | case roles::MATHML_SQUARE_ROOT: |
966 | 0 | case roles::MATHML_STYLE: |
967 | 0 | if (Role() == roles::MATHML_OPERATOR) { |
968 | 0 | // This is an operator inside an <mrow> (or an inferred <mrow>). |
969 | 0 | // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow |
970 | 0 | // XXX We should probably do something similar for MATHML_FENCED, but |
971 | 0 | // operators do not appear in the accessible tree. See bug 1175747. |
972 | 0 | nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame()); |
973 | 0 | if (mathMLFrame) { |
974 | 0 | nsEmbellishData embellishData; |
975 | 0 | mathMLFrame->GetEmbellishData(embellishData); |
976 | 0 | if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) { |
977 | 0 | if (!PrevSibling()) { |
978 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
979 | 0 | nsGkAtoms::open_fence); |
980 | 0 | } else if (!NextSibling()) { |
981 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
982 | 0 | nsGkAtoms::close_fence); |
983 | 0 | } |
984 | 0 | } |
985 | 0 | if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) { |
986 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
987 | 0 | nsGkAtoms::separator_); |
988 | 0 | } |
989 | 0 | } |
990 | 0 | } |
991 | 0 | break; |
992 | 0 | case roles::MATHML_FRACTION: |
993 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
994 | 0 | IndexInParent() == 0 ? |
995 | 0 | nsGkAtoms::numerator : |
996 | 0 | nsGkAtoms::denominator); |
997 | 0 | break; |
998 | 0 | case roles::MATHML_ROOT: |
999 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1000 | 0 | IndexInParent() == 0 ? nsGkAtoms::base : |
1001 | 0 | nsGkAtoms::root_index); |
1002 | 0 | break; |
1003 | 0 | case roles::MATHML_SUB: |
1004 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1005 | 0 | IndexInParent() == 0 ? nsGkAtoms::base : |
1006 | 0 | nsGkAtoms::subscript); |
1007 | 0 | break; |
1008 | 0 | case roles::MATHML_SUP: |
1009 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1010 | 0 | IndexInParent() == 0 ? nsGkAtoms::base : |
1011 | 0 | nsGkAtoms::superscript); |
1012 | 0 | break; |
1013 | 0 | case roles::MATHML_SUB_SUP: { |
1014 | 0 | int32_t index = IndexInParent(); |
1015 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1016 | 0 | index == 0 ? nsGkAtoms::base : |
1017 | 0 | (index == 1 ? nsGkAtoms::subscript : |
1018 | 0 | nsGkAtoms::superscript)); |
1019 | 0 | } break; |
1020 | 0 | case roles::MATHML_UNDER: |
1021 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1022 | 0 | IndexInParent() == 0 ? nsGkAtoms::base : |
1023 | 0 | nsGkAtoms::underscript); |
1024 | 0 | break; |
1025 | 0 | case roles::MATHML_OVER: |
1026 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1027 | 0 | IndexInParent() == 0 ? nsGkAtoms::base : |
1028 | 0 | nsGkAtoms::overscript); |
1029 | 0 | break; |
1030 | 0 | case roles::MATHML_UNDER_OVER: { |
1031 | 0 | int32_t index = IndexInParent(); |
1032 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1033 | 0 | index == 0 ? nsGkAtoms::base : |
1034 | 0 | (index == 1 ? nsGkAtoms::underscript : |
1035 | 0 | nsGkAtoms::overscript)); |
1036 | 0 | } break; |
1037 | 0 | case roles::MATHML_MULTISCRIPTS: { |
1038 | 0 | // Get the <multiscripts> base. |
1039 | 0 | nsIContent* child; |
1040 | 0 | bool baseFound = false; |
1041 | 0 | for (child = parent->GetContent()->GetFirstChild(); child; |
1042 | 0 | child = child->GetNextSibling()) { |
1043 | 0 | if (child->IsMathMLElement()) { |
1044 | 0 | baseFound = true; |
1045 | 0 | break; |
1046 | 0 | } |
1047 | 0 | } |
1048 | 0 | if (baseFound) { |
1049 | 0 | nsIContent* content = GetContent(); |
1050 | 0 | if (child == content) { |
1051 | 0 | // We are the base. |
1052 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1053 | 0 | nsGkAtoms::base); |
1054 | 0 | } else { |
1055 | 0 | // Browse the list of scripts to find us and determine our type. |
1056 | 0 | bool postscript = true; |
1057 | 0 | bool subscript = true; |
1058 | 0 | for (child = child->GetNextSibling(); child; |
1059 | 0 | child = child->GetNextSibling()) { |
1060 | 0 | if (!child->IsMathMLElement()) |
1061 | 0 | continue; |
1062 | 0 | if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) { |
1063 | 0 | postscript = false; |
1064 | 0 | subscript = true; |
1065 | 0 | continue; |
1066 | 0 | } |
1067 | 0 | if (child == content) { |
1068 | 0 | if (postscript) { |
1069 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1070 | 0 | subscript ? |
1071 | 0 | nsGkAtoms::subscript : |
1072 | 0 | nsGkAtoms::superscript); |
1073 | 0 | } else { |
1074 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, |
1075 | 0 | subscript ? |
1076 | 0 | nsGkAtoms::presubscript : |
1077 | 0 | nsGkAtoms::presuperscript); |
1078 | 0 | } |
1079 | 0 | break; |
1080 | 0 | } |
1081 | 0 | subscript = !subscript; |
1082 | 0 | } |
1083 | 0 | } |
1084 | 0 | } |
1085 | 0 | } break; |
1086 | 0 | default: |
1087 | 0 | break; |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | } |
1091 | | |
1092 | | already_AddRefed<nsIPersistentProperties> |
1093 | | HyperTextAccessible::NativeAttributes() |
1094 | 0 | { |
1095 | 0 | nsCOMPtr<nsIPersistentProperties> attributes = |
1096 | 0 | AccessibleWrap::NativeAttributes(); |
1097 | 0 |
|
1098 | 0 | // 'formatting' attribute is deprecated, 'display' attribute should be |
1099 | 0 | // instead. |
1100 | 0 | nsIFrame *frame = GetFrame(); |
1101 | 0 | if (frame && frame->IsBlockFrame()) { |
1102 | 0 | nsAutoString unused; |
1103 | 0 | attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"), |
1104 | 0 | NS_LITERAL_STRING("block"), unused); |
1105 | 0 | } |
1106 | 0 |
|
1107 | 0 | if (FocusMgr()->IsFocused(this)) { |
1108 | 0 | int32_t lineNumber = CaretLineNumber(); |
1109 | 0 | if (lineNumber >= 1) { |
1110 | 0 | nsAutoString strLineNumber; |
1111 | 0 | strLineNumber.AppendInt(lineNumber); |
1112 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber); |
1113 | 0 | } |
1114 | 0 | } |
1115 | 0 |
|
1116 | 0 | if (HasOwnContent()) { |
1117 | 0 | GetAccService()->MarkupAttributes(mContent, attributes); |
1118 | 0 | if (mContent->IsMathMLElement()) |
1119 | 0 | SetMathMLXMLRoles(attributes); |
1120 | 0 | } |
1121 | 0 |
|
1122 | 0 | return attributes.forget(); |
1123 | 0 | } |
1124 | | |
1125 | | nsAtom* |
1126 | | HyperTextAccessible::LandmarkRole() const |
1127 | 0 | { |
1128 | 0 | if (!HasOwnContent()) |
1129 | 0 | return nullptr; |
1130 | 0 | |
1131 | 0 | // For the html landmark elements we expose them like we do ARIA landmarks to |
1132 | 0 | // make AT navigation schemes "just work". |
1133 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::nav)) { |
1134 | 0 | return nsGkAtoms::navigation; |
1135 | 0 | } |
1136 | 0 | |
1137 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::aside)) { |
1138 | 0 | return nsGkAtoms::complementary; |
1139 | 0 | } |
1140 | 0 | |
1141 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::main)) { |
1142 | 0 | return nsGkAtoms::main; |
1143 | 0 | } |
1144 | 0 | |
1145 | 0 | // Only return xml-roles "region" if the section has an accessible name. |
1146 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::section)) { |
1147 | 0 | nsAutoString name; |
1148 | 0 | const_cast<HyperTextAccessible*>(this)->Name(name); |
1149 | 0 | return name.IsEmpty() ? nullptr : nsGkAtoms::region; |
1150 | 0 | } |
1151 | 0 |
|
1152 | 0 | // Only return xml-roles "form" if the form has an accessible name. |
1153 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::form)) { |
1154 | 0 | nsAutoString name; |
1155 | 0 | const_cast<HyperTextAccessible*>(this)->Name(name); |
1156 | 0 | return name.IsEmpty() ? nullptr : nsGkAtoms::form; |
1157 | 0 | } |
1158 | 0 |
|
1159 | 0 | return nullptr; |
1160 | 0 | } |
1161 | | |
1162 | | int32_t |
1163 | | HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType) |
1164 | 0 | { |
1165 | 0 | nsIFrame* hyperFrame = GetFrame(); |
1166 | 0 | if (!hyperFrame) |
1167 | 0 | return -1; |
1168 | 0 | |
1169 | 0 | nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, |
1170 | 0 | this); |
1171 | 0 |
|
1172 | 0 | nsPresContext* presContext = mDoc->PresContext(); |
1173 | 0 | nsPoint coordsInAppUnits = |
1174 | 0 | ToAppUnits(coords, presContext->AppUnitsPerDevPixel()); |
1175 | 0 |
|
1176 | 0 | nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits(); |
1177 | 0 | if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y)) |
1178 | 0 | return -1; // Not found |
1179 | 0 | |
1180 | 0 | nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.X(), |
1181 | 0 | coordsInAppUnits.y - frameScreenRect.Y()); |
1182 | 0 |
|
1183 | 0 | // Go through the frames to check if each one has the point. |
1184 | 0 | // When one does, add up the character offsets until we have a match |
1185 | 0 |
|
1186 | 0 | // We have an point in an accessible child of this, now we need to add up the |
1187 | 0 | // offsets before it to what we already have |
1188 | 0 | int32_t offset = 0; |
1189 | 0 | uint32_t childCount = ChildCount(); |
1190 | 0 | for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { |
1191 | 0 | Accessible* childAcc = mChildren[childIdx]; |
1192 | 0 |
|
1193 | 0 | nsIFrame *primaryFrame = childAcc->GetFrame(); |
1194 | 0 | NS_ENSURE_TRUE(primaryFrame, -1); |
1195 | 0 |
|
1196 | 0 | nsIFrame *frame = primaryFrame; |
1197 | 0 | while (frame) { |
1198 | 0 | nsIContent *content = frame->GetContent(); |
1199 | 0 | NS_ENSURE_TRUE(content, -1); |
1200 | 0 | nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame); |
1201 | 0 | nsSize frameSize = frame->GetSize(); |
1202 | 0 | if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) { |
1203 | 0 | // Finished |
1204 | 0 | if (frame->IsTextFrame()) { |
1205 | 0 | nsIFrame::ContentOffsets contentOffsets = |
1206 | 0 | frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE); |
1207 | 0 | if (contentOffsets.IsNull() || contentOffsets.content != content) { |
1208 | 0 | return -1; // Not found |
1209 | 0 | } |
1210 | 0 | uint32_t addToOffset; |
1211 | 0 | nsresult rv = ContentToRenderedOffset(primaryFrame, |
1212 | 0 | contentOffsets.offset, |
1213 | 0 | &addToOffset); |
1214 | 0 | NS_ENSURE_SUCCESS(rv, -1); |
1215 | 0 | offset += addToOffset; |
1216 | 0 | } |
1217 | 0 | return offset; |
1218 | 0 | } |
1219 | 0 | frame = frame->GetNextContinuation(); |
1220 | 0 | } |
1221 | 0 |
|
1222 | 0 | offset += nsAccUtils::TextLength(childAcc); |
1223 | 0 | } |
1224 | 0 |
|
1225 | 0 | return -1; // Not found |
1226 | 0 | } |
1227 | | |
1228 | | nsIntRect |
1229 | | HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset, |
1230 | | uint32_t aCoordType) |
1231 | 0 | { |
1232 | 0 | index_t startOffset = ConvertMagicOffset(aStartOffset); |
1233 | 0 | index_t endOffset = ConvertMagicOffset(aEndOffset); |
1234 | 0 | if (!startOffset.IsValid() || !endOffset.IsValid() || |
1235 | 0 | startOffset > endOffset || endOffset > CharacterCount()) { |
1236 | 0 | NS_ERROR("Wrong in offset"); |
1237 | 0 | return nsIntRect(); |
1238 | 0 | } |
1239 | 0 |
|
1240 | 0 | if (CharacterCount() == 0) { |
1241 | 0 | nsPresContext* presContext = mDoc->PresContext(); |
1242 | 0 | // Empty content, use our own bound to at least get x,y coordinates |
1243 | 0 | return GetFrame()->GetScreenRectInAppUnits(). |
1244 | 0 | ToNearestPixels(presContext->AppUnitsPerDevPixel()); |
1245 | 0 | } |
1246 | 0 | |
1247 | 0 | int32_t childIdx = GetChildIndexAtOffset(startOffset); |
1248 | 0 | if (childIdx == -1) |
1249 | 0 | return nsIntRect(); |
1250 | 0 | |
1251 | 0 | nsIntRect bounds; |
1252 | 0 | int32_t prevOffset = GetChildOffset(childIdx); |
1253 | 0 | int32_t offset1 = startOffset - prevOffset; |
1254 | 0 |
|
1255 | 0 | while (childIdx < static_cast<int32_t>(ChildCount())) { |
1256 | 0 | nsIFrame* frame = GetChildAt(childIdx++)->GetFrame(); |
1257 | 0 | if (!frame) { |
1258 | 0 | MOZ_ASSERT_UNREACHABLE("No frame for a child!"); |
1259 | 0 | continue; |
1260 | 0 | } |
1261 | 0 |
|
1262 | 0 | int32_t nextOffset = GetChildOffset(childIdx); |
1263 | 0 | if (nextOffset >= static_cast<int32_t>(endOffset)) { |
1264 | 0 | bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, |
1265 | 0 | endOffset - prevOffset)); |
1266 | 0 | break; |
1267 | 0 | } |
1268 | 0 | |
1269 | 0 | bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, |
1270 | 0 | nextOffset - prevOffset)); |
1271 | 0 |
|
1272 | 0 | prevOffset = nextOffset; |
1273 | 0 | offset1 = 0; |
1274 | 0 | } |
1275 | 0 |
|
1276 | 0 | // This document may have a resolution set, we will need to multiply |
1277 | 0 | // the document-relative coordinates by that value and re-apply the doc's |
1278 | 0 | // screen coordinates. |
1279 | 0 | nsPresContext* presContext = mDoc->PresContext(); |
1280 | 0 | nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame(); |
1281 | 0 | nsIntRect orgRectPixels = rootFrame->GetScreenRectInAppUnits().ToNearestPixels(presContext->AppUnitsPerDevPixel()); |
1282 | 0 | bounds.MoveBy(-orgRectPixels.X(), -orgRectPixels.Y()); |
1283 | 0 | bounds.ScaleRoundOut(presContext->PresShell()->GetResolution()); |
1284 | 0 | bounds.MoveBy(orgRectPixels.X(), orgRectPixels.Y()); |
1285 | 0 |
|
1286 | 0 | auto boundsX = bounds.X(); |
1287 | 0 | auto boundsY = bounds.Y(); |
1288 | 0 | nsAccUtils::ConvertScreenCoordsTo(&boundsX, &boundsY, aCoordType, this); |
1289 | 0 | bounds.MoveTo(boundsX, boundsY); |
1290 | 0 | return bounds; |
1291 | 0 | } |
1292 | | |
1293 | | already_AddRefed<TextEditor> |
1294 | | HyperTextAccessible::GetEditor() const |
1295 | 0 | { |
1296 | 0 | if (!mContent->HasFlag(NODE_IS_EDITABLE)) { |
1297 | 0 | // If we're inside an editable container, then return that container's editor |
1298 | 0 | Accessible* ancestor = Parent(); |
1299 | 0 | while (ancestor) { |
1300 | 0 | HyperTextAccessible* hyperText = ancestor->AsHyperText(); |
1301 | 0 | if (hyperText) { |
1302 | 0 | // Recursion will stop at container doc because it has its own impl |
1303 | 0 | // of GetEditor() |
1304 | 0 | return hyperText->GetEditor(); |
1305 | 0 | } |
1306 | 0 | |
1307 | 0 | ancestor = ancestor->Parent(); |
1308 | 0 | } |
1309 | 0 |
|
1310 | 0 | return nullptr; |
1311 | 0 | } |
1312 | 0 | |
1313 | 0 | nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent); |
1314 | 0 | nsCOMPtr<nsIEditingSession> editingSession; |
1315 | 0 | docShell->GetEditingSession(getter_AddRefs(editingSession)); |
1316 | 0 | if (!editingSession) |
1317 | 0 | return nullptr; // No editing session interface |
1318 | 0 | |
1319 | 0 | nsIDocument* docNode = mDoc->DocumentNode(); |
1320 | 0 | RefPtr<HTMLEditor> htmlEditor = |
1321 | 0 | editingSession->GetHTMLEditorForWindow(docNode->GetWindow()); |
1322 | 0 | return htmlEditor.forget(); |
1323 | 0 | } |
1324 | | |
1325 | | /** |
1326 | | * =================== Caret & Selection ====================== |
1327 | | */ |
1328 | | |
1329 | | nsresult |
1330 | | HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos) |
1331 | 0 | { |
1332 | 0 | // Before setting the selection range, we need to ensure that the editor |
1333 | 0 | // is initialized. (See bug 804927.) |
1334 | 0 | // Otherwise, it's possible that lazy editor initialization will override |
1335 | 0 | // the selection we set here and leave the caret at the end of the text. |
1336 | 0 | // By calling GetEditor here, we ensure that editor initialization is |
1337 | 0 | // completed before we set the selection. |
1338 | 0 | RefPtr<TextEditor> textEditor = GetEditor(); |
1339 | 0 |
|
1340 | 0 | bool isFocusable = InteractiveState() & states::FOCUSABLE; |
1341 | 0 |
|
1342 | 0 | // If accessible is focusable then focus it before setting the selection to |
1343 | 0 | // neglect control's selection changes on focus if any (for example, inputs |
1344 | 0 | // that do select all on focus). |
1345 | 0 | // some input controls |
1346 | 0 | if (isFocusable) |
1347 | 0 | TakeFocus(); |
1348 | 0 |
|
1349 | 0 | dom::Selection* domSel = DOMSelection(); |
1350 | 0 | NS_ENSURE_STATE(domSel); |
1351 | 0 |
|
1352 | 0 | // Set up the selection. |
1353 | 0 | for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--) |
1354 | 0 | domSel->RemoveRange(*domSel->GetRangeAt(idx), IgnoreErrors()); |
1355 | 0 | SetSelectionBoundsAt(0, aStartPos, aEndPos); |
1356 | 0 |
|
1357 | 0 | // Make sure it is visible |
1358 | 0 | domSel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, |
1359 | 0 | nsIPresShell::ScrollAxis(), |
1360 | 0 | nsIPresShell::ScrollAxis(), |
1361 | 0 | dom::Selection::SCROLL_FOR_CARET_MOVE | |
1362 | 0 | dom::Selection::SCROLL_OVERFLOW_HIDDEN); |
1363 | 0 |
|
1364 | 0 | // When selection is done, move the focus to the selection if accessible is |
1365 | 0 | // not focusable. That happens when selection is set within hypertext |
1366 | 0 | // accessible. |
1367 | 0 | if (isFocusable) |
1368 | 0 | return NS_OK; |
1369 | 0 | |
1370 | 0 | nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); |
1371 | 0 | if (DOMFocusManager) { |
1372 | 0 | NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE); |
1373 | 0 | nsIDocument* docNode = mDoc->DocumentNode(); |
1374 | 0 | NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE); |
1375 | 0 | nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow(); |
1376 | 0 | RefPtr<dom::Element> result; |
1377 | 0 | DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET, |
1378 | 0 | nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result)); |
1379 | 0 | } |
1380 | 0 |
|
1381 | 0 | return NS_OK; |
1382 | 0 | } |
1383 | | |
1384 | | int32_t |
1385 | | HyperTextAccessible::CaretOffset() const |
1386 | 0 | { |
1387 | 0 | // Not focused focusable accessible except document accessible doesn't have |
1388 | 0 | // a caret. |
1389 | 0 | if (!IsDoc() && !FocusMgr()->IsFocused(this) && |
1390 | 0 | (InteractiveState() & states::FOCUSABLE)) { |
1391 | 0 | return -1; |
1392 | 0 | } |
1393 | 0 | |
1394 | 0 | // Check cached value. |
1395 | 0 | int32_t caretOffset = -1; |
1396 | 0 | HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset); |
1397 | 0 |
|
1398 | 0 | // Use cached value if it corresponds to this accessible. |
1399 | 0 | if (caretOffset != -1) { |
1400 | 0 | if (text == this) |
1401 | 0 | return caretOffset; |
1402 | 0 | |
1403 | 0 | nsINode* textNode = text->GetNode(); |
1404 | 0 | // Ignore offset if cached accessible isn't a text leaf. |
1405 | 0 | if (nsCoreUtils::IsAncestorOf(GetNode(), textNode)) |
1406 | 0 | return TransformOffset(text, |
1407 | 0 | textNode->IsText() ? caretOffset : 0, false); |
1408 | 0 | } |
1409 | 0 |
|
1410 | 0 | // No caret if the focused node is not inside this DOM node and this DOM node |
1411 | 0 | // is not inside of focused node. |
1412 | 0 | FocusManager::FocusDisposition focusDisp = |
1413 | 0 | FocusMgr()->IsInOrContainsFocus(this); |
1414 | 0 | if (focusDisp == FocusManager::eNone) |
1415 | 0 | return -1; |
1416 | 0 | |
1417 | 0 | // Turn the focus node and offset of the selection into caret hypretext |
1418 | 0 | // offset. |
1419 | 0 | dom::Selection* domSel = DOMSelection(); |
1420 | 0 | NS_ENSURE_TRUE(domSel, -1); |
1421 | 0 |
|
1422 | 0 | nsINode* focusNode = domSel->GetFocusNode(); |
1423 | 0 | uint32_t focusOffset = domSel->FocusOffset(); |
1424 | 0 |
|
1425 | 0 | // No caret if this DOM node is inside of focused node but the selection's |
1426 | 0 | // focus point is not inside of this DOM node. |
1427 | 0 | if (focusDisp == FocusManager::eContainedByFocus) { |
1428 | 0 | nsINode* resultNode = |
1429 | 0 | nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset); |
1430 | 0 |
|
1431 | 0 | nsINode* thisNode = GetNode(); |
1432 | 0 | if (resultNode != thisNode && |
1433 | 0 | !nsCoreUtils::IsAncestorOf(thisNode, resultNode)) |
1434 | 0 | return -1; |
1435 | 0 | } |
1436 | 0 | |
1437 | 0 | return DOMPointToOffset(focusNode, focusOffset); |
1438 | 0 | } |
1439 | | |
1440 | | int32_t |
1441 | | HyperTextAccessible::CaretLineNumber() |
1442 | 0 | { |
1443 | 0 | // Provide the line number for the caret, relative to the |
1444 | 0 | // currently focused node. Use a 1-based index |
1445 | 0 | RefPtr<nsFrameSelection> frameSelection = FrameSelection(); |
1446 | 0 | if (!frameSelection) |
1447 | 0 | return -1; |
1448 | 0 | |
1449 | 0 | dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal); |
1450 | 0 | if (!domSel) |
1451 | 0 | return - 1; |
1452 | 0 | |
1453 | 0 | nsINode* caretNode = domSel->GetFocusNode(); |
1454 | 0 | if (!caretNode || !caretNode->IsContent()) |
1455 | 0 | return -1; |
1456 | 0 | |
1457 | 0 | nsIContent* caretContent = caretNode->AsContent(); |
1458 | 0 | if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) |
1459 | 0 | return -1; |
1460 | 0 | |
1461 | 0 | int32_t returnOffsetUnused; |
1462 | 0 | uint32_t caretOffset = domSel->FocusOffset(); |
1463 | 0 | CaretAssociationHint hint = frameSelection->GetHint(); |
1464 | 0 | nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset, |
1465 | 0 | hint, &returnOffsetUnused); |
1466 | 0 | NS_ENSURE_TRUE(caretFrame, -1); |
1467 | 0 |
|
1468 | 0 | int32_t lineNumber = 1; |
1469 | 0 | nsAutoLineIterator lineIterForCaret; |
1470 | 0 | nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr; |
1471 | 0 | while (caretFrame) { |
1472 | 0 | if (hyperTextContent == caretFrame->GetContent()) { |
1473 | 0 | return lineNumber; // Must be in a single line hyper text, there is no line iterator |
1474 | 0 | } |
1475 | 0 | nsContainerFrame *parentFrame = caretFrame->GetParent(); |
1476 | 0 | if (!parentFrame) |
1477 | 0 | break; |
1478 | 0 | |
1479 | 0 | // Add lines for the sibling frames before the caret |
1480 | 0 | nsIFrame *sibling = parentFrame->PrincipalChildList().FirstChild(); |
1481 | 0 | while (sibling && sibling != caretFrame) { |
1482 | 0 | nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator(); |
1483 | 0 | if (lineIterForSibling) { |
1484 | 0 | // For the frames before that grab all the lines |
1485 | 0 | int32_t addLines = lineIterForSibling->GetNumLines(); |
1486 | 0 | lineNumber += addLines; |
1487 | 0 | } |
1488 | 0 | sibling = sibling->GetNextSibling(); |
1489 | 0 | } |
1490 | 0 |
|
1491 | 0 | // Get the line number relative to the container with lines |
1492 | 0 | if (!lineIterForCaret) { // Add the caret line just once |
1493 | 0 | lineIterForCaret = parentFrame->GetLineIterator(); |
1494 | 0 | if (lineIterForCaret) { |
1495 | 0 | // Ancestor of caret |
1496 | 0 | int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame); |
1497 | 0 | lineNumber += addLines; |
1498 | 0 | } |
1499 | 0 | } |
1500 | 0 |
|
1501 | 0 | caretFrame = parentFrame; |
1502 | 0 | } |
1503 | 0 |
|
1504 | 0 | MOZ_ASSERT_UNREACHABLE("DOM ancestry had this hypertext but frame ancestry didn't"); |
1505 | 0 | return lineNumber; |
1506 | 0 | } |
1507 | | |
1508 | | LayoutDeviceIntRect |
1509 | | HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) |
1510 | 0 | { |
1511 | 0 | *aWidget = nullptr; |
1512 | 0 |
|
1513 | 0 | RefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret(); |
1514 | 0 | NS_ENSURE_TRUE(caret, LayoutDeviceIntRect()); |
1515 | 0 |
|
1516 | 0 | bool isVisible = caret->IsVisible(); |
1517 | 0 | if (!isVisible) |
1518 | 0 | return LayoutDeviceIntRect(); |
1519 | 0 | |
1520 | 0 | nsRect rect; |
1521 | 0 | nsIFrame* frame = caret->GetGeometry(&rect); |
1522 | 0 | if (!frame || rect.IsEmpty()) |
1523 | 0 | return LayoutDeviceIntRect(); |
1524 | 0 | |
1525 | 0 | nsPoint offset; |
1526 | 0 | // Offset from widget origin to the frame origin, which includes chrome |
1527 | 0 | // on the widget. |
1528 | 0 | *aWidget = frame->GetNearestWidget(offset); |
1529 | 0 | NS_ENSURE_TRUE(*aWidget, LayoutDeviceIntRect()); |
1530 | 0 | rect.MoveBy(offset); |
1531 | 0 |
|
1532 | 0 | LayoutDeviceIntRect caretRect = LayoutDeviceIntRect::FromUnknownRect( |
1533 | 0 | rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel())); |
1534 | 0 | // ((content screen origin) - (content offset in the widget)) = widget origin on the screen |
1535 | 0 | caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset()); |
1536 | 0 |
|
1537 | 0 | // Correct for character size, so that caret always matches the size of |
1538 | 0 | // the character. This is important for font size transitions, and is |
1539 | 0 | // necessary because the Gecko caret uses the previous character's size as |
1540 | 0 | // the user moves forward in the text by character. |
1541 | 0 | nsIntRect charRect = CharBounds(CaretOffset(), |
1542 | 0 | nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE); |
1543 | 0 | if (!charRect.IsEmpty()) { |
1544 | 0 | caretRect.SetTopEdge(charRect.Y()); |
1545 | 0 | } |
1546 | 0 | return caretRect; |
1547 | 0 | } |
1548 | | |
1549 | | void |
1550 | | HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType, |
1551 | | nsTArray<nsRange*>* aRanges) |
1552 | 0 | { |
1553 | 0 | // Ignore selection if it is not visible. |
1554 | 0 | RefPtr<nsFrameSelection> frameSelection = FrameSelection(); |
1555 | 0 | if (!frameSelection || |
1556 | 0 | frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN) |
1557 | 0 | return; |
1558 | 0 | |
1559 | 0 | dom::Selection* domSel = frameSelection->GetSelection(aSelectionType); |
1560 | 0 | if (!domSel) |
1561 | 0 | return; |
1562 | 0 | |
1563 | 0 | nsINode* startNode = GetNode(); |
1564 | 0 |
|
1565 | 0 | RefPtr<TextEditor> textEditor = GetEditor(); |
1566 | 0 | if (textEditor) { |
1567 | 0 | startNode = textEditor->GetRoot(); |
1568 | 0 | } |
1569 | 0 |
|
1570 | 0 | if (!startNode) |
1571 | 0 | return; |
1572 | 0 | |
1573 | 0 | uint32_t childCount = startNode->GetChildCount(); |
1574 | 0 | nsresult rv = domSel-> |
1575 | 0 | GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges); |
1576 | 0 | NS_ENSURE_SUCCESS_VOID(rv); |
1577 | 0 |
|
1578 | 0 | // Remove collapsed ranges |
1579 | 0 | uint32_t numRanges = aRanges->Length(); |
1580 | 0 | for (uint32_t idx = 0; idx < numRanges; idx ++) { |
1581 | 0 | if ((*aRanges)[idx]->Collapsed()) { |
1582 | 0 | aRanges->RemoveElementAt(idx); |
1583 | 0 | --numRanges; |
1584 | 0 | --idx; |
1585 | 0 | } |
1586 | 0 | } |
1587 | 0 | } |
1588 | | |
1589 | | int32_t |
1590 | | HyperTextAccessible::SelectionCount() |
1591 | 0 | { |
1592 | 0 | nsTArray<nsRange*> ranges; |
1593 | 0 | GetSelectionDOMRanges(SelectionType::eNormal, &ranges); |
1594 | 0 | return ranges.Length(); |
1595 | 0 | } |
1596 | | |
1597 | | bool |
1598 | | HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum, |
1599 | | int32_t* aStartOffset, |
1600 | | int32_t* aEndOffset) |
1601 | 0 | { |
1602 | 0 | *aStartOffset = *aEndOffset = 0; |
1603 | 0 |
|
1604 | 0 | nsTArray<nsRange*> ranges; |
1605 | 0 | GetSelectionDOMRanges(SelectionType::eNormal, &ranges); |
1606 | 0 |
|
1607 | 0 | uint32_t rangeCount = ranges.Length(); |
1608 | 0 | if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount)) |
1609 | 0 | return false; |
1610 | 0 | |
1611 | 0 | nsRange* range = ranges[aSelectionNum]; |
1612 | 0 |
|
1613 | 0 | // Get start and end points. |
1614 | 0 | nsINode* startNode = range->GetStartContainer(); |
1615 | 0 | nsINode* endNode = range->GetEndContainer(); |
1616 | 0 | int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset(); |
1617 | 0 |
|
1618 | 0 | // Make sure start is before end, by swapping DOM points. This occurs when |
1619 | 0 | // the user selects backwards in the text. |
1620 | 0 | int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset, |
1621 | 0 | startNode, startOffset); |
1622 | 0 | if (rangeCompare < 0) { |
1623 | 0 | nsINode* tempNode = startNode; |
1624 | 0 | startNode = endNode; |
1625 | 0 | endNode = tempNode; |
1626 | 0 | int32_t tempOffset = startOffset; |
1627 | 0 | startOffset = endOffset; |
1628 | 0 | endOffset = tempOffset; |
1629 | 0 | } |
1630 | 0 |
|
1631 | 0 | if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent)) |
1632 | 0 | *aStartOffset = 0; |
1633 | 0 | else |
1634 | 0 | *aStartOffset = DOMPointToOffset(startNode, startOffset); |
1635 | 0 |
|
1636 | 0 | if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent)) |
1637 | 0 | *aEndOffset = CharacterCount(); |
1638 | 0 | else |
1639 | 0 | *aEndOffset = DOMPointToOffset(endNode, endOffset, true); |
1640 | 0 | return true; |
1641 | 0 | } |
1642 | | |
1643 | | bool |
1644 | | HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum, |
1645 | | int32_t aStartOffset, |
1646 | | int32_t aEndOffset) |
1647 | 0 | { |
1648 | 0 | index_t startOffset = ConvertMagicOffset(aStartOffset); |
1649 | 0 | index_t endOffset = ConvertMagicOffset(aEndOffset); |
1650 | 0 | if (!startOffset.IsValid() || !endOffset.IsValid() || |
1651 | 0 | std::max(startOffset, endOffset) > CharacterCount()) { |
1652 | 0 | NS_ERROR("Wrong in offset"); |
1653 | 0 | return false; |
1654 | 0 | } |
1655 | 0 |
|
1656 | 0 | dom::Selection* domSel = DOMSelection(); |
1657 | 0 | if (!domSel) |
1658 | 0 | return false; |
1659 | 0 | |
1660 | 0 | RefPtr<nsRange> range; |
1661 | 0 | uint32_t rangeCount = domSel->RangeCount(); |
1662 | 0 | if (aSelectionNum == static_cast<int32_t>(rangeCount)) |
1663 | 0 | range = new nsRange(mContent); |
1664 | 0 | else |
1665 | 0 | range = domSel->GetRangeAt(aSelectionNum); |
1666 | 0 |
|
1667 | 0 | if (!range) |
1668 | 0 | return false; |
1669 | 0 | |
1670 | 0 | if (!OffsetsToDOMRange(std::min(startOffset, endOffset), |
1671 | 0 | std::max(startOffset, endOffset), range)) |
1672 | 0 | return false; |
1673 | 0 | |
1674 | 0 | // If this is not a new range, notify selection listeners that the existing |
1675 | 0 | // selection range has changed. Otherwise, just add the new range. |
1676 | 0 | if (aSelectionNum != static_cast<int32_t>(rangeCount)) { |
1677 | 0 | domSel->RemoveRange(*range, IgnoreErrors()); |
1678 | 0 | } |
1679 | 0 |
|
1680 | 0 | IgnoredErrorResult err; |
1681 | 0 | domSel->AddRange(*range, err); |
1682 | 0 |
|
1683 | 0 | if (!err.Failed()) { |
1684 | 0 | // Changing the direction of the selection assures that the caret |
1685 | 0 | // will be at the logical end of the selection. |
1686 | 0 | domSel->SetDirection(startOffset < endOffset ? eDirNext : eDirPrevious); |
1687 | 0 | return true; |
1688 | 0 | } |
1689 | 0 |
|
1690 | 0 | return false; |
1691 | 0 | } |
1692 | | |
1693 | | bool |
1694 | | HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum) |
1695 | 0 | { |
1696 | 0 | dom::Selection* domSel = DOMSelection(); |
1697 | 0 | if (!domSel) |
1698 | 0 | return false; |
1699 | 0 | |
1700 | 0 | if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(domSel->RangeCount())) |
1701 | 0 | return false; |
1702 | 0 | |
1703 | 0 | domSel->RemoveRange(*domSel->GetRangeAt(aSelectionNum), IgnoreErrors()); |
1704 | 0 | return true; |
1705 | 0 | } |
1706 | | |
1707 | | void |
1708 | | HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset, |
1709 | | uint32_t aScrollType) |
1710 | 0 | { |
1711 | 0 | RefPtr<nsRange> range = new nsRange(mContent); |
1712 | 0 | if (OffsetsToDOMRange(aStartOffset, aEndOffset, range)) |
1713 | 0 | nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType); |
1714 | 0 | } |
1715 | | |
1716 | | void |
1717 | | HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset, |
1718 | | int32_t aEndOffset, |
1719 | | uint32_t aCoordinateType, |
1720 | | int32_t aX, int32_t aY) |
1721 | 0 | { |
1722 | 0 | nsIFrame *frame = GetFrame(); |
1723 | 0 | if (!frame) |
1724 | 0 | return; |
1725 | 0 | |
1726 | 0 | nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, |
1727 | 0 | this); |
1728 | 0 |
|
1729 | 0 | RefPtr<nsRange> range = new nsRange(mContent); |
1730 | 0 | if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range)) |
1731 | 0 | return; |
1732 | 0 | |
1733 | 0 | nsPresContext* presContext = frame->PresContext(); |
1734 | 0 | nsPoint coordsInAppUnits = |
1735 | 0 | ToAppUnits(coords, presContext->AppUnitsPerDevPixel()); |
1736 | 0 |
|
1737 | 0 | bool initialScrolled = false; |
1738 | 0 | nsIFrame *parentFrame = frame; |
1739 | 0 | while ((parentFrame = parentFrame->GetParent())) { |
1740 | 0 | nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame); |
1741 | 0 | if (scrollableFrame) { |
1742 | 0 | if (!initialScrolled) { |
1743 | 0 | // Scroll substring to the given point. Turn the point into percents |
1744 | 0 | // relative scrollable area to use nsCoreUtils::ScrollSubstringTo. |
1745 | 0 | nsRect frameRect = parentFrame->GetScreenRectInAppUnits(); |
1746 | 0 | nscoord offsetPointX = coordsInAppUnits.x - frameRect.X(); |
1747 | 0 | nscoord offsetPointY = coordsInAppUnits.y - frameRect.Y(); |
1748 | 0 |
|
1749 | 0 | nsSize size(parentFrame->GetSize()); |
1750 | 0 |
|
1751 | 0 | // avoid divide by zero |
1752 | 0 | size.width = size.width ? size.width : 1; |
1753 | 0 | size.height = size.height ? size.height : 1; |
1754 | 0 |
|
1755 | 0 | int16_t hPercent = offsetPointX * 100 / size.width; |
1756 | 0 | int16_t vPercent = offsetPointY * 100 / size.height; |
1757 | 0 |
|
1758 | 0 | nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range, |
1759 | 0 | nsIPresShell::ScrollAxis(vPercent), |
1760 | 0 | nsIPresShell::ScrollAxis(hPercent)); |
1761 | 0 | if (NS_FAILED(rv)) |
1762 | 0 | return; |
1763 | 0 | |
1764 | 0 | initialScrolled = true; |
1765 | 0 | } else { |
1766 | 0 | // Substring was scrolled to the given point already inside its closest |
1767 | 0 | // scrollable area. If there are nested scrollable areas then make |
1768 | 0 | // sure we scroll lower areas to the given point inside currently |
1769 | 0 | // traversed scrollable area. |
1770 | 0 | nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); |
1771 | 0 | } |
1772 | 0 | } |
1773 | 0 | frame = parentFrame; |
1774 | 0 | } |
1775 | 0 | } |
1776 | | |
1777 | | void |
1778 | | HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const |
1779 | 0 | { |
1780 | 0 | if (IsTextField()) { |
1781 | 0 | aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0, |
1782 | 0 | const_cast<HyperTextAccessible*>(this), CharacterCount()); |
1783 | 0 | } else { |
1784 | 0 | aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount()); |
1785 | 0 | } |
1786 | 0 | } |
1787 | | |
1788 | | void |
1789 | | HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const |
1790 | 0 | { |
1791 | 0 | MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty"); |
1792 | 0 |
|
1793 | 0 | dom::Selection* sel = DOMSelection(); |
1794 | 0 | if (!sel) |
1795 | 0 | return; |
1796 | 0 | |
1797 | 0 | aRanges->SetCapacity(sel->RangeCount()); |
1798 | 0 |
|
1799 | 0 | for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) { |
1800 | 0 | nsRange* DOMRange = sel->GetRangeAt(idx); |
1801 | 0 | HyperTextAccessible* startContainer = |
1802 | 0 | nsAccUtils::GetTextContainer(DOMRange->GetStartContainer()); |
1803 | 0 | HyperTextAccessible* endContainer = |
1804 | 0 | nsAccUtils::GetTextContainer(DOMRange->GetEndContainer()); |
1805 | 0 | if (!startContainer || !endContainer) { |
1806 | 0 | continue; |
1807 | 0 | } |
1808 | 0 | |
1809 | 0 | int32_t startOffset = |
1810 | 0 | startContainer->DOMPointToOffset(DOMRange->GetStartContainer(), |
1811 | 0 | DOMRange->StartOffset(), false); |
1812 | 0 | int32_t endOffset = |
1813 | 0 | endContainer->DOMPointToOffset(DOMRange->GetEndContainer(), |
1814 | 0 | DOMRange->EndOffset(), true); |
1815 | 0 |
|
1816 | 0 | TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc, |
1817 | 0 | startContainer, startOffset, endContainer, endOffset); |
1818 | 0 | *(aRanges->AppendElement()) = std::move(tr); |
1819 | 0 | } |
1820 | 0 | } |
1821 | | |
1822 | | void |
1823 | | HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const |
1824 | 0 | { |
1825 | 0 | } |
1826 | | |
1827 | | void |
1828 | | HyperTextAccessible::RangeByChild(Accessible* aChild, |
1829 | | a11y::TextRange& aRange) const |
1830 | 0 | { |
1831 | 0 | HyperTextAccessible* ht = aChild->AsHyperText(); |
1832 | 0 | if (ht) { |
1833 | 0 | aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount()); |
1834 | 0 | return; |
1835 | 0 | } |
1836 | 0 | |
1837 | 0 | Accessible* child = aChild; |
1838 | 0 | Accessible* parent = nullptr; |
1839 | 0 | while ((parent = child->Parent()) && !(ht = parent->AsHyperText())) |
1840 | 0 | child = parent; |
1841 | 0 |
|
1842 | 0 | // If no text then return collapsed text range, otherwise return a range |
1843 | 0 | // containing the text enclosed by the given child. |
1844 | 0 | if (ht) { |
1845 | 0 | int32_t childIdx = child->IndexInParent(); |
1846 | 0 | int32_t startOffset = ht->GetChildOffset(childIdx); |
1847 | 0 | int32_t endOffset = child->IsTextLeaf() ? |
1848 | 0 | ht->GetChildOffset(childIdx + 1) : startOffset; |
1849 | 0 | aRange.Set(mDoc, ht, startOffset, ht, endOffset); |
1850 | 0 | } |
1851 | 0 | } |
1852 | | |
1853 | | void |
1854 | | HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY, |
1855 | | a11y::TextRange& aRange) const |
1856 | 0 | { |
1857 | 0 | Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild); |
1858 | 0 | if (!child) |
1859 | 0 | return; |
1860 | 0 | |
1861 | 0 | Accessible* parent = nullptr; |
1862 | 0 | while ((parent = child->Parent()) && !parent->IsHyperText()) |
1863 | 0 | child = parent; |
1864 | 0 |
|
1865 | 0 | // Return collapsed text range for the point. |
1866 | 0 | if (parent) { |
1867 | 0 | HyperTextAccessible* ht = parent->AsHyperText(); |
1868 | 0 | int32_t offset = ht->GetChildOffset(child); |
1869 | 0 | aRange.Set(mDoc, ht, offset, ht, offset); |
1870 | 0 | } |
1871 | 0 | } |
1872 | | |
1873 | | //////////////////////////////////////////////////////////////////////////////// |
1874 | | // Accessible public |
1875 | | |
1876 | | // Accessible protected |
1877 | | ENameValueFlag |
1878 | | HyperTextAccessible::NativeName(nsString& aName) const |
1879 | 0 | { |
1880 | 0 | // Check @alt attribute for invalid img elements. |
1881 | 0 | bool hasImgAlt = false; |
1882 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::img)) { |
1883 | 0 | hasImgAlt = |
1884 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName); |
1885 | 0 | if (!aName.IsEmpty()) |
1886 | 0 | return eNameOK; |
1887 | 0 | } |
1888 | 0 | |
1889 | 0 | ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName); |
1890 | 0 | if (!aName.IsEmpty()) |
1891 | 0 | return nameFlag; |
1892 | 0 | |
1893 | 0 | // Get name from title attribute for HTML abbr and acronym elements making it |
1894 | 0 | // a valid name from markup. Otherwise their name isn't picked up by recursive |
1895 | 0 | // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP. |
1896 | 0 | if (IsAbbreviation() && |
1897 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) |
1898 | 0 | aName.CompressWhitespace(); |
1899 | 0 |
|
1900 | 0 | return hasImgAlt ? eNoNameOnPurpose : eNameOK; |
1901 | 0 | } |
1902 | | |
1903 | | void |
1904 | | HyperTextAccessible::Shutdown() |
1905 | 0 | { |
1906 | 0 | mOffsets.Clear(); |
1907 | 0 | AccessibleWrap::Shutdown(); |
1908 | 0 | } |
1909 | | |
1910 | | bool |
1911 | | HyperTextAccessible::RemoveChild(Accessible* aAccessible) |
1912 | 0 | { |
1913 | 0 | int32_t childIndex = aAccessible->IndexInParent(); |
1914 | 0 | int32_t count = mOffsets.Length() - childIndex; |
1915 | 0 | if (count > 0) |
1916 | 0 | mOffsets.RemoveElementsAt(childIndex, count); |
1917 | 0 |
|
1918 | 0 | return AccessibleWrap::RemoveChild(aAccessible); |
1919 | 0 | } |
1920 | | |
1921 | | bool |
1922 | | HyperTextAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) |
1923 | 0 | { |
1924 | 0 | int32_t count = mOffsets.Length() - aIndex; |
1925 | 0 | if (count > 0 ) { |
1926 | 0 | mOffsets.RemoveElementsAt(aIndex, count); |
1927 | 0 | } |
1928 | 0 | return AccessibleWrap::InsertChildAt(aIndex, aChild); |
1929 | 0 | } |
1930 | | |
1931 | | Relation |
1932 | | HyperTextAccessible::RelationByType(RelationType aType) const |
1933 | 0 | { |
1934 | 0 | Relation rel = Accessible::RelationByType(aType); |
1935 | 0 |
|
1936 | 0 | switch (aType) { |
1937 | 0 | case RelationType::NODE_CHILD_OF: |
1938 | 0 | if (HasOwnContent() && mContent->IsMathMLElement()) { |
1939 | 0 | Accessible* parent = Parent(); |
1940 | 0 | if (parent) { |
1941 | 0 | nsIContent* parentContent = parent->GetContent(); |
1942 | 0 | if (parentContent && |
1943 | 0 | parentContent->IsMathMLElement(nsGkAtoms::mroot_)) { |
1944 | 0 | // Add a relation pointing to the parent <mroot>. |
1945 | 0 | rel.AppendTarget(parent); |
1946 | 0 | } |
1947 | 0 | } |
1948 | 0 | } |
1949 | 0 | break; |
1950 | 0 | case RelationType::NODE_PARENT_OF: |
1951 | 0 | if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) { |
1952 | 0 | Accessible* base = GetChildAt(0); |
1953 | 0 | Accessible* index = GetChildAt(1); |
1954 | 0 | if (base && index) { |
1955 | 0 | // Append the <mroot> children in the order index, base. |
1956 | 0 | rel.AppendTarget(index); |
1957 | 0 | rel.AppendTarget(base); |
1958 | 0 | } |
1959 | 0 | } |
1960 | 0 | break; |
1961 | 0 | default: |
1962 | 0 | break; |
1963 | 0 | } |
1964 | 0 | |
1965 | 0 | return rel; |
1966 | 0 | } |
1967 | | |
1968 | | //////////////////////////////////////////////////////////////////////////////// |
1969 | | // HyperTextAccessible public static |
1970 | | |
1971 | | nsresult |
1972 | | HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset, |
1973 | | uint32_t* aRenderedOffset) const |
1974 | 0 | { |
1975 | 0 | if (!aFrame) { |
1976 | 0 | // Current frame not rendered -- this can happen if text is set on |
1977 | 0 | // something with display: none |
1978 | 0 | *aRenderedOffset = 0; |
1979 | 0 | return NS_OK; |
1980 | 0 | } |
1981 | 0 | |
1982 | 0 | if (IsTextField()) { |
1983 | 0 | *aRenderedOffset = aContentOffset; |
1984 | 0 | return NS_OK; |
1985 | 0 | } |
1986 | 0 | |
1987 | 0 | NS_ASSERTION(aFrame->IsTextFrame(), "Need text frame for offset conversion"); |
1988 | 0 | NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, |
1989 | 0 | "Call on primary frame only"); |
1990 | 0 |
|
1991 | 0 | nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset, |
1992 | 0 | aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, |
1993 | 0 | nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); |
1994 | 0 | *aRenderedOffset = text.mOffsetWithinNodeRenderedText; |
1995 | 0 |
|
1996 | 0 | return NS_OK; |
1997 | 0 | } |
1998 | | |
1999 | | nsresult |
2000 | | HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset, |
2001 | | int32_t* aContentOffset) const |
2002 | 0 | { |
2003 | 0 | if (IsTextField()) { |
2004 | 0 | *aContentOffset = aRenderedOffset; |
2005 | 0 | return NS_OK; |
2006 | 0 | } |
2007 | 0 | |
2008 | 0 | *aContentOffset = 0; |
2009 | 0 | NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE); |
2010 | 0 |
|
2011 | 0 | NS_ASSERTION(aFrame->IsTextFrame(), "Need text frame for offset conversion"); |
2012 | 0 | NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, |
2013 | 0 | "Call on primary frame only"); |
2014 | 0 |
|
2015 | 0 | nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset, |
2016 | 0 | aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT, |
2017 | 0 | nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); |
2018 | 0 | *aContentOffset = text.mOffsetWithinNodeText; |
2019 | 0 |
|
2020 | 0 | return NS_OK; |
2021 | 0 | } |
2022 | | |
2023 | | //////////////////////////////////////////////////////////////////////////////// |
2024 | | // HyperTextAccessible public |
2025 | | |
2026 | | int32_t |
2027 | | HyperTextAccessible::GetChildOffset(uint32_t aChildIndex, |
2028 | | bool aInvalidateAfter) const |
2029 | 0 | { |
2030 | 0 | if (aChildIndex == 0) { |
2031 | 0 | if (aInvalidateAfter) |
2032 | 0 | mOffsets.Clear(); |
2033 | 0 |
|
2034 | 0 | return aChildIndex; |
2035 | 0 | } |
2036 | 0 |
|
2037 | 0 | int32_t count = mOffsets.Length() - aChildIndex; |
2038 | 0 | if (count > 0) { |
2039 | 0 | if (aInvalidateAfter) |
2040 | 0 | mOffsets.RemoveElementsAt(aChildIndex, count); |
2041 | 0 |
|
2042 | 0 | return mOffsets[aChildIndex - 1]; |
2043 | 0 | } |
2044 | 0 |
|
2045 | 0 | uint32_t lastOffset = mOffsets.IsEmpty() ? |
2046 | 0 | 0 : mOffsets[mOffsets.Length() - 1]; |
2047 | 0 |
|
2048 | 0 | while (mOffsets.Length() < aChildIndex) { |
2049 | 0 | Accessible* child = mChildren[mOffsets.Length()]; |
2050 | 0 | lastOffset += nsAccUtils::TextLength(child); |
2051 | 0 | mOffsets.AppendElement(lastOffset); |
2052 | 0 | } |
2053 | 0 |
|
2054 | 0 | return mOffsets[aChildIndex - 1]; |
2055 | 0 | } |
2056 | | |
2057 | | int32_t |
2058 | | HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const |
2059 | 0 | { |
2060 | 0 | uint32_t lastOffset = 0; |
2061 | 0 | const uint32_t offsetCount = mOffsets.Length(); |
2062 | 0 |
|
2063 | 0 | if (offsetCount > 0) { |
2064 | 0 | lastOffset = mOffsets[offsetCount - 1]; |
2065 | 0 | if (aOffset < lastOffset) { |
2066 | 0 | size_t index; |
2067 | 0 | if (BinarySearch(mOffsets, 0, offsetCount, aOffset, &index)) { |
2068 | 0 | return (index < (offsetCount - 1)) ? index + 1 : index; |
2069 | 0 | } |
2070 | 0 |
|
2071 | 0 | return (index == offsetCount) ? -1 : index; |
2072 | 0 | } |
2073 | 0 | } |
2074 | 0 |
|
2075 | 0 | uint32_t childCount = ChildCount(); |
2076 | 0 | while (mOffsets.Length() < childCount) { |
2077 | 0 | Accessible* child = GetChildAt(mOffsets.Length()); |
2078 | 0 | lastOffset += nsAccUtils::TextLength(child); |
2079 | 0 | mOffsets.AppendElement(lastOffset); |
2080 | 0 | if (aOffset < lastOffset) |
2081 | 0 | return mOffsets.Length() - 1; |
2082 | 0 | } |
2083 | 0 |
|
2084 | 0 | if (aOffset == lastOffset) |
2085 | 0 | return mOffsets.Length() - 1; |
2086 | 0 | |
2087 | 0 | return -1; |
2088 | 0 | } |
2089 | | |
2090 | | //////////////////////////////////////////////////////////////////////////////// |
2091 | | // HyperTextAccessible protected |
2092 | | |
2093 | | nsresult |
2094 | | HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset, |
2095 | | Accessible* aAccessible, |
2096 | | DOMPoint* aPoint) |
2097 | 0 | { |
2098 | 0 | NS_ENSURE_ARG(aAccessible); |
2099 | 0 |
|
2100 | 0 | if (!aFrame) { |
2101 | 0 | // If the given frame is null then set offset after the DOM node of the |
2102 | 0 | // given accessible. |
2103 | 0 | NS_ASSERTION(!aAccessible->IsDoc(), |
2104 | 0 | "Shouldn't be called on document accessible!"); |
2105 | 0 |
|
2106 | 0 | nsIContent* content = aAccessible->GetContent(); |
2107 | 0 | NS_ASSERTION(content, "Shouldn't operate on defunct accessible!"); |
2108 | 0 |
|
2109 | 0 | nsIContent* parent = content->GetParent(); |
2110 | 0 |
|
2111 | 0 | aPoint->idx = parent->ComputeIndexOf(content) + 1; |
2112 | 0 | aPoint->node = parent; |
2113 | 0 |
|
2114 | 0 | } else if (aFrame->IsTextFrame()) { |
2115 | 0 | nsIContent* content = aFrame->GetContent(); |
2116 | 0 | NS_ENSURE_STATE(content); |
2117 | 0 |
|
2118 | 0 | nsIFrame *primaryFrame = content->GetPrimaryFrame(); |
2119 | 0 | nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx)); |
2120 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2121 | 0 |
|
2122 | 0 | aPoint->node = content; |
2123 | 0 |
|
2124 | 0 | } else { |
2125 | 0 | nsIContent* content = aFrame->GetContent(); |
2126 | 0 | NS_ENSURE_STATE(content); |
2127 | 0 |
|
2128 | 0 | nsIContent* parent = content->GetParent(); |
2129 | 0 | NS_ENSURE_STATE(parent); |
2130 | 0 |
|
2131 | 0 | aPoint->idx = parent->ComputeIndexOf(content); |
2132 | 0 | aPoint->node = parent; |
2133 | 0 | } |
2134 | 0 |
|
2135 | 0 | return NS_OK; |
2136 | 0 | } |
2137 | | |
2138 | | // HyperTextAccessible |
2139 | | void |
2140 | | HyperTextAccessible::GetSpellTextAttr(nsINode* aNode, |
2141 | | int32_t aNodeOffset, |
2142 | | uint32_t* aStartOffset, |
2143 | | uint32_t* aEndOffset, |
2144 | | nsIPersistentProperties* aAttributes) |
2145 | 0 | { |
2146 | 0 | RefPtr<nsFrameSelection> fs = FrameSelection(); |
2147 | 0 | if (!fs) |
2148 | 0 | return; |
2149 | 0 | |
2150 | 0 | dom::Selection* domSel = fs->GetSelection(SelectionType::eSpellCheck); |
2151 | 0 | if (!domSel) |
2152 | 0 | return; |
2153 | 0 | |
2154 | 0 | int32_t rangeCount = domSel->RangeCount(); |
2155 | 0 | if (rangeCount <= 0) |
2156 | 0 | return; |
2157 | 0 | |
2158 | 0 | uint32_t startOffset = 0, endOffset = 0; |
2159 | 0 | for (int32_t idx = 0; idx < rangeCount; idx++) { |
2160 | 0 | nsRange* range = domSel->GetRangeAt(idx); |
2161 | 0 | if (range->Collapsed()) |
2162 | 0 | continue; |
2163 | 0 | |
2164 | 0 | // See if the point comes after the range in which case we must continue in |
2165 | 0 | // case there is another range after this one. |
2166 | 0 | nsINode* endNode = range->GetEndContainer(); |
2167 | 0 | int32_t endNodeOffset = range->EndOffset(); |
2168 | 0 | if (nsContentUtils::ComparePoints(aNode, aNodeOffset, |
2169 | 0 | endNode, endNodeOffset) >= 0) |
2170 | 0 | continue; |
2171 | 0 | |
2172 | 0 | // At this point our point is either in this range or before it but after |
2173 | 0 | // the previous range. So we check to see if the range starts before the |
2174 | 0 | // point in which case the point is in the missspelled range, otherwise it |
2175 | 0 | // must be before the range and after the previous one if any. |
2176 | 0 | nsINode* startNode = range->GetStartContainer(); |
2177 | 0 | int32_t startNodeOffset = range->StartOffset(); |
2178 | 0 | if (nsContentUtils::ComparePoints(startNode, startNodeOffset, aNode, |
2179 | 0 | aNodeOffset) <= 0) { |
2180 | 0 | startOffset = DOMPointToOffset(startNode, startNodeOffset); |
2181 | 0 |
|
2182 | 0 | endOffset = DOMPointToOffset(endNode, endNodeOffset); |
2183 | 0 |
|
2184 | 0 | if (startOffset > *aStartOffset) |
2185 | 0 | *aStartOffset = startOffset; |
2186 | 0 |
|
2187 | 0 | if (endOffset < *aEndOffset) |
2188 | 0 | *aEndOffset = endOffset; |
2189 | 0 |
|
2190 | 0 | if (aAttributes) { |
2191 | 0 | nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, |
2192 | 0 | NS_LITERAL_STRING("spelling")); |
2193 | 0 | } |
2194 | 0 |
|
2195 | 0 | return; |
2196 | 0 | } |
2197 | 0 |
|
2198 | 0 | // This range came after the point. |
2199 | 0 | endOffset = DOMPointToOffset(startNode, startNodeOffset); |
2200 | 0 |
|
2201 | 0 | if (idx > 0) { |
2202 | 0 | nsRange* prevRange = domSel->GetRangeAt(idx - 1); |
2203 | 0 | startOffset = DOMPointToOffset(prevRange->GetEndContainer(), |
2204 | 0 | prevRange->EndOffset()); |
2205 | 0 | } |
2206 | 0 |
|
2207 | 0 | // The previous range might not be within this accessible. In that case, |
2208 | 0 | // DOMPointToOffset returns length as a fallback. We don't want to use |
2209 | 0 | // that offset if so, hence the startOffset < *aEndOffset check. |
2210 | 0 | if (startOffset > *aStartOffset && startOffset < *aEndOffset) |
2211 | 0 | *aStartOffset = startOffset; |
2212 | 0 |
|
2213 | 0 | if (endOffset < *aEndOffset) |
2214 | 0 | *aEndOffset = endOffset; |
2215 | 0 |
|
2216 | 0 | return; |
2217 | 0 | } |
2218 | 0 |
|
2219 | 0 | // We never found a range that ended after the point, therefore we know that |
2220 | 0 | // the point is not in a range, that we do not need to compute an end offset, |
2221 | 0 | // and that we should use the end offset of the last range to compute the |
2222 | 0 | // start offset of the text attribute range. |
2223 | 0 | nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1); |
2224 | 0 | startOffset = DOMPointToOffset(prevRange->GetEndContainer(), |
2225 | 0 | prevRange->EndOffset()); |
2226 | 0 |
|
2227 | 0 | // The previous range might not be within this accessible. In that case, |
2228 | 0 | // DOMPointToOffset returns length as a fallback. We don't want to use |
2229 | 0 | // that offset if so, hence the startOffset < *aEndOffset check. |
2230 | 0 | if (startOffset > *aStartOffset && startOffset < *aEndOffset) |
2231 | 0 | *aStartOffset = startOffset; |
2232 | 0 | } |
2233 | | |
2234 | | bool |
2235 | | HyperTextAccessible::IsTextRole() |
2236 | 0 | { |
2237 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
2238 | 0 | if (roleMapEntry && |
2239 | 0 | (roleMapEntry->role == roles::GRAPHIC || |
2240 | 0 | roleMapEntry->role == roles::IMAGE_MAP || |
2241 | 0 | roleMapEntry->role == roles::SLIDER || |
2242 | 0 | roleMapEntry->role == roles::PROGRESSBAR || |
2243 | 0 | roleMapEntry->role == roles::SEPARATOR)) |
2244 | 0 | return false; |
2245 | 0 | |
2246 | 0 | return true; |
2247 | 0 | } |