|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
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 "nsAccessiblePivot.h" |
|
8 |
|
9 #include "HyperTextAccessible.h" |
|
10 #include "nsAccUtils.h" |
|
11 #include "States.h" |
|
12 |
|
13 using namespace mozilla::a11y; |
|
14 |
|
15 |
|
16 /** |
|
17 * An object that stores a given traversal rule during |
|
18 */ |
|
19 class RuleCache |
|
20 { |
|
21 public: |
|
22 RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule), |
|
23 mAcceptRoles(nullptr) { } |
|
24 ~RuleCache () { |
|
25 if (mAcceptRoles) |
|
26 nsMemory::Free(mAcceptRoles); |
|
27 } |
|
28 |
|
29 nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult); |
|
30 |
|
31 private: |
|
32 nsCOMPtr<nsIAccessibleTraversalRule> mRule; |
|
33 uint32_t* mAcceptRoles; |
|
34 uint32_t mAcceptRolesLength; |
|
35 uint32_t mPreFilter; |
|
36 }; |
|
37 |
|
38 //////////////////////////////////////////////////////////////////////////////// |
|
39 // nsAccessiblePivot |
|
40 |
|
41 nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot) : |
|
42 mRoot(aRoot), mModalRoot(nullptr), mPosition(nullptr), |
|
43 mStartOffset(-1), mEndOffset(-1) |
|
44 { |
|
45 NS_ASSERTION(aRoot, "A root accessible is required"); |
|
46 } |
|
47 |
|
48 //////////////////////////////////////////////////////////////////////////////// |
|
49 // nsISupports |
|
50 |
|
51 NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot, mRoot, mPosition, mObservers) |
|
52 |
|
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot) |
|
54 NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot) |
|
55 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot) |
|
56 NS_INTERFACE_MAP_END |
|
57 |
|
58 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot) |
|
59 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot) |
|
60 |
|
61 //////////////////////////////////////////////////////////////////////////////// |
|
62 // nsIAccessiblePivot |
|
63 |
|
64 NS_IMETHODIMP |
|
65 nsAccessiblePivot::GetRoot(nsIAccessible** aRoot) |
|
66 { |
|
67 NS_ENSURE_ARG_POINTER(aRoot); |
|
68 |
|
69 NS_IF_ADDREF(*aRoot = mRoot); |
|
70 |
|
71 return NS_OK; |
|
72 } |
|
73 |
|
74 NS_IMETHODIMP |
|
75 nsAccessiblePivot::GetPosition(nsIAccessible** aPosition) |
|
76 { |
|
77 NS_ENSURE_ARG_POINTER(aPosition); |
|
78 |
|
79 NS_IF_ADDREF(*aPosition = mPosition); |
|
80 |
|
81 return NS_OK; |
|
82 } |
|
83 |
|
84 NS_IMETHODIMP |
|
85 nsAccessiblePivot::SetPosition(nsIAccessible* aPosition) |
|
86 { |
|
87 nsRefPtr<Accessible> secondPosition; |
|
88 |
|
89 if (aPosition) { |
|
90 secondPosition = do_QueryObject(aPosition); |
|
91 if (!secondPosition || !IsDescendantOf(secondPosition, GetActiveRoot())) |
|
92 return NS_ERROR_INVALID_ARG; |
|
93 } |
|
94 |
|
95 // Swap old position with new position, saves us an AddRef/Release. |
|
96 mPosition.swap(secondPosition); |
|
97 int32_t oldStart = mStartOffset, oldEnd = mEndOffset; |
|
98 mStartOffset = mEndOffset = -1; |
|
99 NotifyOfPivotChange(secondPosition, oldStart, oldEnd, |
|
100 nsIAccessiblePivot::REASON_NONE); |
|
101 |
|
102 return NS_OK; |
|
103 } |
|
104 |
|
105 NS_IMETHODIMP |
|
106 nsAccessiblePivot::GetModalRoot(nsIAccessible** aModalRoot) |
|
107 { |
|
108 NS_ENSURE_ARG_POINTER(aModalRoot); |
|
109 |
|
110 NS_IF_ADDREF(*aModalRoot = mModalRoot); |
|
111 |
|
112 return NS_OK; |
|
113 } |
|
114 |
|
115 NS_IMETHODIMP |
|
116 nsAccessiblePivot::SetModalRoot(nsIAccessible* aModalRoot) |
|
117 { |
|
118 nsRefPtr<Accessible> modalRoot; |
|
119 |
|
120 if (aModalRoot) { |
|
121 modalRoot = do_QueryObject(aModalRoot); |
|
122 if (!modalRoot || !IsDescendantOf(modalRoot, mRoot)) |
|
123 return NS_ERROR_INVALID_ARG; |
|
124 } |
|
125 |
|
126 mModalRoot.swap(modalRoot); |
|
127 |
|
128 return NS_OK; |
|
129 } |
|
130 |
|
131 NS_IMETHODIMP |
|
132 nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset) |
|
133 { |
|
134 NS_ENSURE_ARG_POINTER(aStartOffset); |
|
135 |
|
136 *aStartOffset = mStartOffset; |
|
137 |
|
138 return NS_OK; |
|
139 } |
|
140 |
|
141 NS_IMETHODIMP |
|
142 nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset) |
|
143 { |
|
144 NS_ENSURE_ARG_POINTER(aEndOffset); |
|
145 |
|
146 *aEndOffset = mEndOffset; |
|
147 |
|
148 return NS_OK; |
|
149 } |
|
150 |
|
151 NS_IMETHODIMP |
|
152 nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible, |
|
153 int32_t aStartOffset, int32_t aEndOffset) |
|
154 { |
|
155 NS_ENSURE_ARG(aTextAccessible); |
|
156 |
|
157 // Check that start offset is smaller than end offset, and that if a value is |
|
158 // smaller than 0, both should be -1. |
|
159 NS_ENSURE_TRUE(aStartOffset <= aEndOffset && |
|
160 (aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)), |
|
161 NS_ERROR_INVALID_ARG); |
|
162 |
|
163 nsRefPtr<Accessible> acc(do_QueryObject(aTextAccessible)); |
|
164 if (!acc) |
|
165 return NS_ERROR_INVALID_ARG; |
|
166 |
|
167 HyperTextAccessible* newPosition = acc->AsHyperText(); |
|
168 if (!newPosition || !IsDescendantOf(newPosition, GetActiveRoot())) |
|
169 return NS_ERROR_INVALID_ARG; |
|
170 |
|
171 // Make sure the given offsets don't exceed the character count. |
|
172 int32_t charCount = newPosition->CharacterCount(); |
|
173 |
|
174 if (aEndOffset > charCount) |
|
175 return NS_ERROR_FAILURE; |
|
176 |
|
177 int32_t oldStart = mStartOffset, oldEnd = mEndOffset; |
|
178 mStartOffset = aStartOffset; |
|
179 mEndOffset = aEndOffset; |
|
180 |
|
181 nsRefPtr<Accessible> oldPosition = mPosition.forget(); |
|
182 mPosition = newPosition; |
|
183 |
|
184 NotifyOfPivotChange(oldPosition, oldStart, oldEnd, |
|
185 nsIAccessiblePivot::REASON_TEXT); |
|
186 |
|
187 return NS_OK; |
|
188 } |
|
189 |
|
190 // Traversal functions |
|
191 |
|
192 NS_IMETHODIMP |
|
193 nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, |
|
194 nsIAccessible* aAnchor, bool aIncludeStart, |
|
195 uint8_t aArgc, bool* aResult) |
|
196 { |
|
197 NS_ENSURE_ARG(aResult); |
|
198 NS_ENSURE_ARG(aRule); |
|
199 |
|
200 *aResult = false; |
|
201 |
|
202 Accessible* root = GetActiveRoot(); |
|
203 nsRefPtr<Accessible> anchor = |
|
204 (aArgc > 0) ? do_QueryObject(aAnchor) : mPosition; |
|
205 if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, root))) |
|
206 return NS_ERROR_NOT_IN_TREE; |
|
207 |
|
208 nsresult rv = NS_OK; |
|
209 Accessible* accessible = |
|
210 SearchForward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv); |
|
211 NS_ENSURE_SUCCESS(rv, rv); |
|
212 |
|
213 if (accessible) |
|
214 *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT); |
|
215 |
|
216 return NS_OK; |
|
217 } |
|
218 |
|
219 NS_IMETHODIMP |
|
220 nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule, |
|
221 nsIAccessible* aAnchor, |
|
222 bool aIncludeStart, |
|
223 uint8_t aArgc, bool* aResult) |
|
224 { |
|
225 NS_ENSURE_ARG(aResult); |
|
226 NS_ENSURE_ARG(aRule); |
|
227 |
|
228 *aResult = false; |
|
229 |
|
230 Accessible* root = GetActiveRoot(); |
|
231 nsRefPtr<Accessible> anchor = |
|
232 (aArgc > 0) ? do_QueryObject(aAnchor) : mPosition; |
|
233 if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, root))) |
|
234 return NS_ERROR_NOT_IN_TREE; |
|
235 |
|
236 nsresult rv = NS_OK; |
|
237 Accessible* accessible = |
|
238 SearchBackward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv); |
|
239 NS_ENSURE_SUCCESS(rv, rv); |
|
240 |
|
241 if (accessible) |
|
242 *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV); |
|
243 |
|
244 return NS_OK; |
|
245 } |
|
246 |
|
247 NS_IMETHODIMP |
|
248 nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult) |
|
249 { |
|
250 NS_ENSURE_ARG(aResult); |
|
251 NS_ENSURE_ARG(aRule); |
|
252 |
|
253 Accessible* root = GetActiveRoot(); |
|
254 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE); |
|
255 |
|
256 nsresult rv = NS_OK; |
|
257 Accessible* accessible = SearchForward(root, aRule, true, &rv); |
|
258 NS_ENSURE_SUCCESS(rv, rv); |
|
259 |
|
260 if (accessible) |
|
261 *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST); |
|
262 |
|
263 return NS_OK; |
|
264 } |
|
265 |
|
266 NS_IMETHODIMP |
|
267 nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule, |
|
268 bool* aResult) |
|
269 { |
|
270 NS_ENSURE_ARG(aResult); |
|
271 NS_ENSURE_ARG(aRule); |
|
272 |
|
273 Accessible* root = GetActiveRoot(); |
|
274 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE); |
|
275 |
|
276 *aResult = false; |
|
277 nsresult rv = NS_OK; |
|
278 Accessible* lastAccessible = root; |
|
279 Accessible* accessible = nullptr; |
|
280 |
|
281 // First go to the last accessible in pre-order |
|
282 while (lastAccessible->HasChildren()) |
|
283 lastAccessible = lastAccessible->LastChild(); |
|
284 |
|
285 // Search backwards from last accessible and find the last occurrence in the doc |
|
286 accessible = SearchBackward(lastAccessible, aRule, true, &rv); |
|
287 NS_ENSURE_SUCCESS(rv, rv); |
|
288 |
|
289 if (accessible) |
|
290 *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST); |
|
291 |
|
292 return NS_OK; |
|
293 } |
|
294 |
|
295 NS_IMETHODIMP |
|
296 nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult) |
|
297 { |
|
298 NS_ENSURE_ARG(aResult); |
|
299 |
|
300 *aResult = false; |
|
301 |
|
302 int32_t tempStart = mStartOffset, tempEnd = mEndOffset; |
|
303 Accessible* tempPosition = mPosition; |
|
304 Accessible* root = GetActiveRoot(); |
|
305 while (true) { |
|
306 Accessible* curPosition = tempPosition; |
|
307 HyperTextAccessible* text = nullptr; |
|
308 // Find the nearest text node using a preorder traversal starting from |
|
309 // the current node. |
|
310 if (!(text = tempPosition->AsHyperText())) { |
|
311 text = SearchForText(tempPosition, false); |
|
312 if (!text) |
|
313 return NS_OK; |
|
314 if (text != curPosition) |
|
315 tempStart = tempEnd = -1; |
|
316 tempPosition = text; |
|
317 } |
|
318 |
|
319 // If the search led to the parent of the node we started on (e.g. when |
|
320 // starting on a text leaf), start the text movement from the end of that |
|
321 // node, otherwise we just default to 0. |
|
322 if (tempEnd == -1) |
|
323 tempEnd = text == curPosition->Parent() ? |
|
324 text->GetChildOffset(curPosition) : 0; |
|
325 |
|
326 // If there's no more text on the current node, try to find the next text |
|
327 // node; if there isn't one, bail out. |
|
328 if (tempEnd == text->CharacterCount()) { |
|
329 if (tempPosition == root) |
|
330 return NS_OK; |
|
331 |
|
332 // If we're currently sitting on a link, try move to either the next |
|
333 // sibling or the parent, whichever is closer to the current end |
|
334 // offset. Otherwise, do a forward search for the next node to land on |
|
335 // (we don't do this in the first case because we don't want to go to the |
|
336 // subtree). |
|
337 Accessible* sibling = tempPosition->NextSibling(); |
|
338 if (tempPosition->IsLink()) { |
|
339 if (sibling && sibling->IsLink()) { |
|
340 tempStart = tempEnd = -1; |
|
341 tempPosition = sibling; |
|
342 } else { |
|
343 tempStart = tempPosition->StartOffset(); |
|
344 tempEnd = tempPosition->EndOffset(); |
|
345 tempPosition = tempPosition->Parent(); |
|
346 } |
|
347 } else { |
|
348 tempPosition = SearchForText(tempPosition, false); |
|
349 if (!tempPosition) |
|
350 return NS_OK; |
|
351 tempStart = tempEnd = -1; |
|
352 } |
|
353 continue; |
|
354 } |
|
355 |
|
356 AccessibleTextBoundary startBoundary, endBoundary; |
|
357 switch (aBoundary) { |
|
358 case CHAR_BOUNDARY: |
|
359 startBoundary = nsIAccessibleText::BOUNDARY_CHAR; |
|
360 endBoundary = nsIAccessibleText::BOUNDARY_CHAR; |
|
361 break; |
|
362 case WORD_BOUNDARY: |
|
363 startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; |
|
364 endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; |
|
365 break; |
|
366 default: |
|
367 return NS_ERROR_INVALID_ARG; |
|
368 } |
|
369 |
|
370 nsAutoString unusedText; |
|
371 int32_t newStart = 0, newEnd = 0, currentEnd = tempEnd; |
|
372 text->TextAtOffset(tempEnd, endBoundary, &newStart, &tempEnd, unusedText); |
|
373 text->TextBeforeOffset(tempEnd, startBoundary, &newStart, &newEnd, unusedText); |
|
374 int32_t potentialStart = newEnd == tempEnd ? newStart : newEnd; |
|
375 tempStart = potentialStart > tempStart ? potentialStart : currentEnd; |
|
376 |
|
377 // The offset range we've obtained might have embedded characters in it, |
|
378 // limit the range to the start of the first occurrence of an embedded |
|
379 // character. |
|
380 Accessible* childAtOffset = nullptr; |
|
381 for (int32_t i = tempStart; i < tempEnd; i++) { |
|
382 childAtOffset = text->GetChildAtOffset(i); |
|
383 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) { |
|
384 tempEnd = i; |
|
385 break; |
|
386 } |
|
387 } |
|
388 // If there's an embedded character at the very start of the range, we |
|
389 // instead want to traverse into it. So restart the movement with |
|
390 // the child as the starting point. |
|
391 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) && |
|
392 tempStart == childAtOffset->StartOffset()) { |
|
393 tempPosition = childAtOffset; |
|
394 tempStart = tempEnd = -1; |
|
395 continue; |
|
396 } |
|
397 |
|
398 *aResult = true; |
|
399 |
|
400 Accessible* startPosition = mPosition; |
|
401 int32_t oldStart = mStartOffset, oldEnd = mEndOffset; |
|
402 mPosition = tempPosition; |
|
403 mStartOffset = tempStart; |
|
404 mEndOffset = tempEnd; |
|
405 NotifyOfPivotChange(startPosition, oldStart, oldEnd, |
|
406 nsIAccessiblePivot::REASON_TEXT); |
|
407 return NS_OK; |
|
408 } |
|
409 } |
|
410 |
|
411 NS_IMETHODIMP |
|
412 nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) |
|
413 { |
|
414 NS_ENSURE_ARG(aResult); |
|
415 |
|
416 *aResult = false; |
|
417 |
|
418 int32_t tempStart = mStartOffset, tempEnd = mEndOffset; |
|
419 Accessible* tempPosition = mPosition; |
|
420 Accessible* root = GetActiveRoot(); |
|
421 while (true) { |
|
422 Accessible* curPosition = tempPosition; |
|
423 HyperTextAccessible* text; |
|
424 // Find the nearest text node using a reverse preorder traversal starting |
|
425 // from the current node. |
|
426 if (!(text = tempPosition->AsHyperText())) { |
|
427 text = SearchForText(tempPosition, true); |
|
428 if (!text) |
|
429 return NS_OK; |
|
430 if (text != curPosition) |
|
431 tempStart = tempEnd = -1; |
|
432 tempPosition = text; |
|
433 } |
|
434 |
|
435 // If the search led to the parent of the node we started on (e.g. when |
|
436 // starting on a text leaf), start the text movement from the end of that |
|
437 // node, otherwise we just default to 0. |
|
438 if (tempStart == -1) { |
|
439 if (tempPosition != curPosition) |
|
440 tempStart = text == curPosition->Parent() ? |
|
441 text->GetChildOffset(curPosition) : text->CharacterCount(); |
|
442 else |
|
443 tempStart = 0; |
|
444 } |
|
445 |
|
446 // If there's no more text on the current node, try to find the previous |
|
447 // text node; if there isn't one, bail out. |
|
448 if (tempStart == 0) { |
|
449 if (tempPosition == root) |
|
450 return NS_OK; |
|
451 |
|
452 // If we're currently sitting on a link, try move to either the previous |
|
453 // sibling or the parent, whichever is closer to the current end |
|
454 // offset. Otherwise, do a forward search for the next node to land on |
|
455 // (we don't do this in the first case because we don't want to go to the |
|
456 // subtree). |
|
457 Accessible* sibling = tempPosition->PrevSibling(); |
|
458 if (tempPosition->IsLink()) { |
|
459 if (sibling && sibling->IsLink()) { |
|
460 HyperTextAccessible* siblingText = sibling->AsHyperText(); |
|
461 tempStart = tempEnd = siblingText ? |
|
462 siblingText->CharacterCount() : -1; |
|
463 tempPosition = sibling; |
|
464 } else { |
|
465 tempStart = tempPosition->StartOffset(); |
|
466 tempEnd = tempPosition->EndOffset(); |
|
467 tempPosition = tempPosition->Parent(); |
|
468 } |
|
469 } else { |
|
470 HyperTextAccessible* tempText = SearchForText(tempPosition, true); |
|
471 if (!tempText) |
|
472 return NS_OK; |
|
473 tempPosition = tempText; |
|
474 tempStart = tempEnd = tempText->CharacterCount(); |
|
475 } |
|
476 continue; |
|
477 } |
|
478 |
|
479 AccessibleTextBoundary startBoundary, endBoundary; |
|
480 switch (aBoundary) { |
|
481 case CHAR_BOUNDARY: |
|
482 startBoundary = nsIAccessibleText::BOUNDARY_CHAR; |
|
483 endBoundary = nsIAccessibleText::BOUNDARY_CHAR; |
|
484 break; |
|
485 case WORD_BOUNDARY: |
|
486 startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; |
|
487 endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; |
|
488 break; |
|
489 default: |
|
490 return NS_ERROR_INVALID_ARG; |
|
491 } |
|
492 |
|
493 nsAutoString unusedText; |
|
494 int32_t newStart = 0, newEnd = 0, currentStart = tempStart, potentialEnd = 0; |
|
495 text->TextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd, unusedText); |
|
496 if (newStart < tempStart) |
|
497 tempStart = newEnd >= currentStart ? newStart : newEnd; |
|
498 else // XXX: In certain odd cases newStart is equal to tempStart |
|
499 text->TextBeforeOffset(tempStart - 1, startBoundary, &newStart, |
|
500 &tempStart, unusedText); |
|
501 text->TextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd, |
|
502 unusedText); |
|
503 tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart; |
|
504 |
|
505 // The offset range we've obtained might have embedded characters in it, |
|
506 // limit the range to the start of the last occurrence of an embedded |
|
507 // character. |
|
508 Accessible* childAtOffset = nullptr; |
|
509 for (int32_t i = tempEnd - 1; i >= tempStart; i--) { |
|
510 childAtOffset = text->GetChildAtOffset(i); |
|
511 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) { |
|
512 tempStart = childAtOffset->EndOffset(); |
|
513 break; |
|
514 } |
|
515 } |
|
516 // If there's an embedded character at the very end of the range, we |
|
517 // instead want to traverse into it. So restart the movement with |
|
518 // the child as the starting point. |
|
519 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) && |
|
520 tempEnd == childAtOffset->EndOffset()) { |
|
521 tempPosition = childAtOffset; |
|
522 tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount(); |
|
523 continue; |
|
524 } |
|
525 |
|
526 *aResult = true; |
|
527 |
|
528 Accessible* startPosition = mPosition; |
|
529 int32_t oldStart = mStartOffset, oldEnd = mEndOffset; |
|
530 mPosition = tempPosition; |
|
531 mStartOffset = tempStart; |
|
532 mEndOffset = tempEnd; |
|
533 |
|
534 NotifyOfPivotChange(startPosition, oldStart, oldEnd, |
|
535 nsIAccessiblePivot::REASON_TEXT); |
|
536 return NS_OK; |
|
537 } |
|
538 } |
|
539 |
|
540 NS_IMETHODIMP |
|
541 nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule, |
|
542 int32_t aX, int32_t aY, bool aIgnoreNoMatch, |
|
543 bool* aResult) |
|
544 { |
|
545 NS_ENSURE_ARG_POINTER(aResult); |
|
546 NS_ENSURE_ARG_POINTER(aRule); |
|
547 |
|
548 *aResult = false; |
|
549 |
|
550 Accessible* root = GetActiveRoot(); |
|
551 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE); |
|
552 |
|
553 RuleCache cache(aRule); |
|
554 Accessible* match = nullptr; |
|
555 Accessible* child = root->ChildAtPoint(aX, aY, Accessible::eDeepestChild); |
|
556 while (child && root != child) { |
|
557 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; |
|
558 nsresult rv = cache.ApplyFilter(child, &filtered); |
|
559 NS_ENSURE_SUCCESS(rv, rv); |
|
560 |
|
561 // Ignore any matching nodes that were below this one |
|
562 if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) |
|
563 match = nullptr; |
|
564 |
|
565 // Match if no node below this is a match |
|
566 if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) { |
|
567 int32_t childX, childY, childWidth, childHeight; |
|
568 child->GetBounds(&childX, &childY, &childWidth, &childHeight); |
|
569 // Double-check child's bounds since the deepest child may have been out |
|
570 // of bounds. This assures we don't return a false positive. |
|
571 if (aX >= childX && aX < childX + childWidth && |
|
572 aY >= childY && aY < childY + childHeight) |
|
573 match = child; |
|
574 } |
|
575 |
|
576 child = child->Parent(); |
|
577 } |
|
578 |
|
579 if (match || !aIgnoreNoMatch) |
|
580 *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT); |
|
581 |
|
582 return NS_OK; |
|
583 } |
|
584 |
|
585 // Observer functions |
|
586 |
|
587 NS_IMETHODIMP |
|
588 nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver) |
|
589 { |
|
590 NS_ENSURE_ARG(aObserver); |
|
591 |
|
592 mObservers.AppendElement(aObserver); |
|
593 |
|
594 return NS_OK; |
|
595 } |
|
596 |
|
597 NS_IMETHODIMP |
|
598 nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver) |
|
599 { |
|
600 NS_ENSURE_ARG(aObserver); |
|
601 |
|
602 return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE; |
|
603 } |
|
604 |
|
605 // Private utility methods |
|
606 |
|
607 bool |
|
608 nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor) |
|
609 { |
|
610 if (!aAncestor || aAncestor->IsDefunct()) |
|
611 return false; |
|
612 |
|
613 // XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875. |
|
614 Accessible* accessible = aAccessible; |
|
615 do { |
|
616 if (accessible == aAncestor) |
|
617 return true; |
|
618 } while ((accessible = accessible->Parent())); |
|
619 |
|
620 return false; |
|
621 } |
|
622 |
|
623 bool |
|
624 nsAccessiblePivot::MovePivotInternal(Accessible* aPosition, |
|
625 PivotMoveReason aReason) |
|
626 { |
|
627 nsRefPtr<Accessible> oldPosition = mPosition.forget(); |
|
628 mPosition = aPosition; |
|
629 int32_t oldStart = mStartOffset, oldEnd = mEndOffset; |
|
630 mStartOffset = mEndOffset = -1; |
|
631 |
|
632 return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason); |
|
633 } |
|
634 |
|
635 Accessible* |
|
636 nsAccessiblePivot::AdjustStartPosition(Accessible* aAccessible, |
|
637 RuleCache& aCache, |
|
638 uint16_t* aFilterResult, |
|
639 nsresult* aResult) |
|
640 { |
|
641 Accessible* matched = aAccessible; |
|
642 *aResult = aCache.ApplyFilter(aAccessible, aFilterResult); |
|
643 |
|
644 if (aAccessible != mRoot && aAccessible != mModalRoot) { |
|
645 for (Accessible* temp = aAccessible->Parent(); |
|
646 temp && temp != mRoot && temp != mModalRoot; temp = temp->Parent()) { |
|
647 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; |
|
648 *aResult = aCache.ApplyFilter(temp, &filtered); |
|
649 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
650 if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) { |
|
651 *aFilterResult = filtered; |
|
652 matched = temp; |
|
653 } |
|
654 } |
|
655 } |
|
656 |
|
657 return matched; |
|
658 } |
|
659 |
|
660 Accessible* |
|
661 nsAccessiblePivot::SearchBackward(Accessible* aAccessible, |
|
662 nsIAccessibleTraversalRule* aRule, |
|
663 bool aSearchCurrent, |
|
664 nsresult* aResult) |
|
665 { |
|
666 *aResult = NS_OK; |
|
667 |
|
668 // Initial position could be unset, in that case return null. |
|
669 if (!aAccessible) |
|
670 return nullptr; |
|
671 |
|
672 RuleCache cache(aRule); |
|
673 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; |
|
674 Accessible* accessible = AdjustStartPosition(aAccessible, cache, |
|
675 &filtered, aResult); |
|
676 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
677 |
|
678 if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) { |
|
679 return accessible; |
|
680 } |
|
681 |
|
682 Accessible* root = GetActiveRoot(); |
|
683 while (accessible != root) { |
|
684 Accessible* parent = accessible->Parent(); |
|
685 int32_t idxInParent = accessible->IndexInParent(); |
|
686 while (idxInParent > 0) { |
|
687 if (!(accessible = parent->GetChildAt(--idxInParent))) |
|
688 continue; |
|
689 |
|
690 *aResult = cache.ApplyFilter(accessible, &filtered); |
|
691 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
692 |
|
693 Accessible* lastChild = nullptr; |
|
694 while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && |
|
695 (lastChild = accessible->LastChild())) { |
|
696 parent = accessible; |
|
697 accessible = lastChild; |
|
698 idxInParent = accessible->IndexInParent(); |
|
699 *aResult = cache.ApplyFilter(accessible, &filtered); |
|
700 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
701 } |
|
702 |
|
703 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) |
|
704 return accessible; |
|
705 } |
|
706 |
|
707 if (!(accessible = parent)) |
|
708 break; |
|
709 |
|
710 *aResult = cache.ApplyFilter(accessible, &filtered); |
|
711 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
712 |
|
713 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) |
|
714 return accessible; |
|
715 } |
|
716 |
|
717 return nullptr; |
|
718 } |
|
719 |
|
720 Accessible* |
|
721 nsAccessiblePivot::SearchForward(Accessible* aAccessible, |
|
722 nsIAccessibleTraversalRule* aRule, |
|
723 bool aSearchCurrent, |
|
724 nsresult* aResult) |
|
725 { |
|
726 *aResult = NS_OK; |
|
727 |
|
728 // Initial position could be not set, in that case begin search from root. |
|
729 Accessible* root = GetActiveRoot(); |
|
730 Accessible* accessible = (!aAccessible) ? root : aAccessible; |
|
731 |
|
732 RuleCache cache(aRule); |
|
733 |
|
734 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; |
|
735 accessible = AdjustStartPosition(accessible, cache, &filtered, aResult); |
|
736 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
737 if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) |
|
738 return accessible; |
|
739 |
|
740 while (true) { |
|
741 Accessible* firstChild = nullptr; |
|
742 while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && |
|
743 (firstChild = accessible->FirstChild())) { |
|
744 accessible = firstChild; |
|
745 *aResult = cache.ApplyFilter(accessible, &filtered); |
|
746 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
747 |
|
748 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) |
|
749 return accessible; |
|
750 } |
|
751 |
|
752 Accessible* sibling = nullptr; |
|
753 Accessible* temp = accessible; |
|
754 do { |
|
755 if (temp == root) |
|
756 break; |
|
757 |
|
758 sibling = temp->NextSibling(); |
|
759 |
|
760 if (sibling) |
|
761 break; |
|
762 } while ((temp = temp->Parent())); |
|
763 |
|
764 if (!sibling) |
|
765 break; |
|
766 |
|
767 accessible = sibling; |
|
768 *aResult = cache.ApplyFilter(accessible, &filtered); |
|
769 NS_ENSURE_SUCCESS(*aResult, nullptr); |
|
770 |
|
771 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) |
|
772 return accessible; |
|
773 } |
|
774 |
|
775 return nullptr; |
|
776 } |
|
777 |
|
778 HyperTextAccessible* |
|
779 nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward) |
|
780 { |
|
781 Accessible* root = GetActiveRoot(); |
|
782 Accessible* accessible = aAccessible; |
|
783 while (true) { |
|
784 Accessible* child = nullptr; |
|
785 |
|
786 while ((child = (aBackward ? accessible->LastChild() : |
|
787 accessible->FirstChild()))) { |
|
788 accessible = child; |
|
789 if (child->IsHyperText()) |
|
790 return child->AsHyperText(); |
|
791 } |
|
792 |
|
793 Accessible* sibling = nullptr; |
|
794 Accessible* temp = accessible; |
|
795 do { |
|
796 if (temp == root) |
|
797 break; |
|
798 |
|
799 if (temp != aAccessible && temp->IsHyperText()) |
|
800 return temp->AsHyperText(); |
|
801 |
|
802 sibling = aBackward ? temp->PrevSibling() : temp->NextSibling(); |
|
803 |
|
804 if (sibling) |
|
805 break; |
|
806 } while ((temp = temp->Parent())); |
|
807 |
|
808 if (!sibling) |
|
809 break; |
|
810 |
|
811 accessible = sibling; |
|
812 if (accessible->IsHyperText()) |
|
813 return accessible->AsHyperText(); |
|
814 } |
|
815 |
|
816 return nullptr; |
|
817 } |
|
818 |
|
819 |
|
820 bool |
|
821 nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition, |
|
822 int32_t aOldStart, int32_t aOldEnd, |
|
823 int16_t aReason) |
|
824 { |
|
825 if (aOldPosition == mPosition && |
|
826 aOldStart == mStartOffset && aOldEnd == mEndOffset) |
|
827 return false; |
|
828 |
|
829 nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers); |
|
830 while (iter.HasMore()) { |
|
831 nsIAccessiblePivotObserver* obs = iter.GetNext(); |
|
832 obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd, aReason); |
|
833 } |
|
834 |
|
835 return true; |
|
836 } |
|
837 |
|
838 nsresult |
|
839 RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult) |
|
840 { |
|
841 *aResult = nsIAccessibleTraversalRule::FILTER_IGNORE; |
|
842 |
|
843 if (!mAcceptRoles) { |
|
844 nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength); |
|
845 NS_ENSURE_SUCCESS(rv, rv); |
|
846 rv = mRule->GetPreFilter(&mPreFilter); |
|
847 NS_ENSURE_SUCCESS(rv, rv); |
|
848 } |
|
849 |
|
850 if (mPreFilter) { |
|
851 uint64_t state = aAccessible->State(); |
|
852 |
|
853 if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) && |
|
854 (state & states::INVISIBLE)) |
|
855 return NS_OK; |
|
856 |
|
857 if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) && |
|
858 (state & states::OFFSCREEN)) |
|
859 return NS_OK; |
|
860 |
|
861 if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) && |
|
862 !(state & states::FOCUSABLE)) |
|
863 return NS_OK; |
|
864 |
|
865 if (nsIAccessibleTraversalRule::PREFILTER_ARIA_HIDDEN & mPreFilter) { |
|
866 nsIContent* content = aAccessible->GetContent(); |
|
867 if (content && |
|
868 nsAccUtils::HasDefinedARIAToken(content, nsGkAtoms::aria_hidden) && |
|
869 !content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_hidden, |
|
870 nsGkAtoms::_false, eCaseMatters)) { |
|
871 *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; |
|
872 return NS_OK; |
|
873 } |
|
874 } |
|
875 |
|
876 if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) && |
|
877 !(state & states::OPAQUE1)) { |
|
878 nsIFrame* frame = aAccessible->GetFrame(); |
|
879 if (frame->StyleDisplay()->mOpacity == 0.0f) { |
|
880 *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; |
|
881 return NS_OK; |
|
882 } |
|
883 } |
|
884 } |
|
885 |
|
886 if (mAcceptRolesLength > 0) { |
|
887 uint32_t accessibleRole = aAccessible->Role(); |
|
888 bool matchesRole = false; |
|
889 for (uint32_t idx = 0; idx < mAcceptRolesLength; idx++) { |
|
890 matchesRole = mAcceptRoles[idx] == accessibleRole; |
|
891 if (matchesRole) |
|
892 break; |
|
893 } |
|
894 if (!matchesRole) |
|
895 return NS_OK; |
|
896 } |
|
897 |
|
898 return mRule->Match(aAccessible, aResult); |
|
899 } |