Coverage Report

Created: 2018-09-25 14:53

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