Coverage Report

Created: 2018-09-25 14:53

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