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