1/* clang-format off */
2/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3/* clang-format on */
4/* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8#include "DocAccessibleParent.h"
9#include "AccAttributes.h"
10#include "AccessibleOrProxy.h"
11#include "nsCocoaUtils.h"
12
13#include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
14
15#import "GeckoTextMarker.h"
16
17extern "C" {
18
19CFTypeID AXTextMarkerGetTypeID();
20
21AXTextMarkerRef AXTextMarkerCreate(CFAllocatorRef allocator, const UInt8* bytes,
22                                   CFIndex length);
23
24const UInt8* AXTextMarkerGetBytePtr(AXTextMarkerRef text_marker);
25
26size_t AXTextMarkerGetLength(AXTextMarkerRef text_marker);
27
28CFTypeID AXTextMarkerRangeGetTypeID();
29
30AXTextMarkerRangeRef AXTextMarkerRangeCreate(CFAllocatorRef allocator,
31                                             AXTextMarkerRef start_marker,
32                                             AXTextMarkerRef end_marker);
33
34AXTextMarkerRef AXTextMarkerRangeCopyStartMarker(
35    AXTextMarkerRangeRef text_marker_range);
36
37AXTextMarkerRef AXTextMarkerRangeCopyEndMarker(
38    AXTextMarkerRangeRef text_marker_range);
39}
40
41namespace mozilla {
42namespace a11y {
43
44struct OpaqueGeckoTextMarker {
45  OpaqueGeckoTextMarker(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
46      : mDoc(aDoc), mID(aID), mOffset(aOffset) {}
47  OpaqueGeckoTextMarker() {}
48  uintptr_t mDoc;
49  uintptr_t mID;
50  int32_t mOffset;
51};
52
53static bool DocumentExists(AccessibleOrProxy aDoc, uintptr_t aDocPtr) {
54  if (aDoc.Bits() == aDocPtr) {
55    return true;
56  }
57
58  if (aDoc.IsAccessible()) {
59    DocAccessible* docAcc = aDoc.AsAccessible()->AsDoc();
60    uint32_t docCount = docAcc->ChildDocumentCount();
61    for (uint32_t i = 0; i < docCount; i++) {
62      if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) {
63        return true;
64      }
65    }
66  } else {
67    DocAccessibleParent* docProxy = aDoc.AsProxy()->AsDoc();
68    size_t docCount = docProxy->ChildDocCount();
69    for (uint32_t i = 0; i < docCount; i++) {
70      if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) {
71        return true;
72      }
73    }
74  }
75
76  return false;
77}
78
79// GeckoTextMarker
80
81GeckoTextMarker::GeckoTextMarker(AccessibleOrProxy aDoc,
82                                 AXTextMarkerRef aTextMarker) {
83  MOZ_ASSERT(!aDoc.IsNull());
84  OpaqueGeckoTextMarker opaqueMarker;
85  if (aTextMarker &&
86      AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
87    memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker),
88           sizeof(OpaqueGeckoTextMarker));
89    if (DocumentExists(aDoc, opaqueMarker.mDoc)) {
90      AccessibleOrProxy doc;
91      doc.SetBits(opaqueMarker.mDoc);
92      if (doc.IsProxy()) {
93        mContainer = doc.AsProxy()->AsDoc()->GetAccessible(opaqueMarker.mID);
94      } else {
95        mContainer = doc.AsAccessible()->AsDoc()->GetAccessibleByUniqueID(
96            reinterpret_cast<void*>(opaqueMarker.mID));
97      }
98    }
99
100    mOffset = opaqueMarker.mOffset;
101  }
102}
103
104GeckoTextMarker GeckoTextMarker::MarkerFromIndex(const AccessibleOrProxy& aRoot,
105                                                 int32_t aIndex) {
106  if (aRoot.IsProxy()) {
107    int32_t offset = 0;
108    uint64_t containerID = 0;
109    DocAccessibleParent* ipcDoc = aRoot.AsProxy()->Document();
110    Unused << ipcDoc->GetPlatformExtension()->SendOffsetAtIndex(
111        aRoot.AsProxy()->ID(), aIndex, &containerID, &offset);
112    RemoteAccessible* container = ipcDoc->GetAccessible(containerID);
113    return GeckoTextMarker(container, offset);
114  } else if (auto htWrap = static_cast<HyperTextAccessibleWrap*>(
115                 aRoot.AsAccessible()->AsHyperText())) {
116    int32_t offset = 0;
117    HyperTextAccessible* container = nullptr;
118    htWrap->OffsetAtIndex(aIndex, &container, &offset);
119    return GeckoTextMarker(container, offset);
120  }
121
122  return GeckoTextMarker();
123}
124
125id GeckoTextMarker::CreateAXTextMarker() {
126  if (!IsValid()) {
127    return nil;
128  }
129
130  AccessibleOrProxy doc;
131  if (mContainer.IsProxy()) {
132    doc = mContainer.AsProxy()->Document();
133  } else {
134    doc = mContainer.AsAccessible()->Document();
135  }
136
137  uintptr_t identifier =
138      mContainer.IsProxy()
139          ? mContainer.AsProxy()->ID()
140          : reinterpret_cast<uintptr_t>(mContainer.AsAccessible()->UniqueID());
141
142  OpaqueGeckoTextMarker opaqueMarker(doc.Bits(), identifier, mOffset);
143  AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
144      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&opaqueMarker),
145      sizeof(OpaqueGeckoTextMarker));
146
147  return [static_cast<id>(cf_text_marker) autorelease];
148}
149
150bool GeckoTextMarker::operator<(const GeckoTextMarker& aPoint) const {
151  if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
152
153  // Build the chain of parents
154  AutoTArray<AccessibleOrProxy, 30> parents1, parents2;
155  AccessibleOrProxy p1 = mContainer;
156  while (!p1.IsNull()) {
157    parents1.AppendElement(p1);
158    p1 = p1.Parent();
159  }
160
161  AccessibleOrProxy p2 = aPoint.mContainer;
162  while (!p2.IsNull()) {
163    parents2.AppendElement(p2);
164    p2 = p2.Parent();
165  }
166
167  // An empty chain of parents means one of the containers was null.
168  MOZ_ASSERT(parents1.Length() != 0 && parents2.Length() != 0,
169             "have empty chain of parents!");
170
171  // Find where the parent chain differs
172  uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
173  for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
174    AccessibleOrProxy child1 = parents1.ElementAt(--pos1);
175    AccessibleOrProxy child2 = parents2.ElementAt(--pos2);
176    if (child1 != child2) {
177      return child1.IndexInParent() < child2.IndexInParent();
178    }
179  }
180
181  if (pos1 != 0) {
182    // If parents1 is a superset of parents2 then mContainer is a
183    // descendant of aPoint.mContainer. The next element down in parents1
184    // is mContainer's ancestor that is the child of aPoint.mContainer.
185    // We compare its end offset in aPoint.mContainer with aPoint.mOffset.
186    AccessibleOrProxy child = parents1.ElementAt(pos1 - 1);
187    MOZ_ASSERT(child.Parent() == aPoint.mContainer);
188    bool unused;
189    uint32_t endOffset = child.IsProxy() ? child.AsProxy()->EndOffset(&unused)
190                                         : child.AsAccessible()->EndOffset();
191    return endOffset < static_cast<uint32_t>(aPoint.mOffset);
192  }
193
194  if (pos2 != 0) {
195    // If parents2 is a superset of parents1 then aPoint.mContainer is a
196    // descendant of mContainer. The next element down in parents2
197    // is aPoint.mContainer's ancestor that is the child of mContainer.
198    // We compare its start offset in mContainer with mOffset.
199    AccessibleOrProxy child = parents2.ElementAt(pos2 - 1);
200    MOZ_ASSERT(child.Parent() == mContainer);
201    bool unused;
202    uint32_t startOffset = child.IsProxy()
203                               ? child.AsProxy()->StartOffset(&unused)
204                               : child.AsAccessible()->StartOffset();
205    return static_cast<uint32_t>(mOffset) <= startOffset;
206  }
207
208  MOZ_ASSERT_UNREACHABLE("Broken tree?!");
209  return false;
210}
211
212bool GeckoTextMarker::IsEditableRoot() {
213  uint64_t state = mContainer.IsProxy() ? mContainer.AsProxy()->State()
214                                        : mContainer.AsAccessible()->State();
215  if ((state & states::EDITABLE) == 0) {
216    return false;
217  }
218
219  AccessibleOrProxy parent = mContainer.Parent();
220  if (parent.IsNull()) {
221    // Not sure when this can happen, but it would technically be an editable
222    // root.
223    return true;
224  }
225
226  state = parent.IsProxy() ? parent.AsProxy()->State()
227                           : parent.AsAccessible()->State();
228
229  return (state & states::EDITABLE) == 0;
230}
231
232bool GeckoTextMarker::Next() {
233  if (mContainer.IsProxy()) {
234    int32_t nextOffset = 0;
235    uint64_t nextContainerID = 0;
236    DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
237    Unused << ipcDoc->GetPlatformExtension()->SendNextClusterAt(
238        mContainer.AsProxy()->ID(), mOffset, &nextContainerID, &nextOffset);
239    RemoteAccessible* nextContainer = ipcDoc->GetAccessible(nextContainerID);
240    bool moved = nextContainer != mContainer.AsProxy() || nextOffset != mOffset;
241    mContainer = nextContainer;
242    mOffset = nextOffset;
243    return moved;
244  } else if (auto htWrap = ContainerAsHyperTextWrap()) {
245    HyperTextAccessible* nextContainer = nullptr;
246    int32_t nextOffset = 0;
247    htWrap->NextClusterAt(mOffset, &nextContainer, &nextOffset);
248    bool moved = nextContainer != htWrap || nextOffset != mOffset;
249    mContainer = nextContainer;
250    mOffset = nextOffset;
251    return moved;
252  }
253
254  return false;
255}
256
257bool GeckoTextMarker::Previous() {
258  if (mContainer.IsProxy()) {
259    int32_t prevOffset = 0;
260    uint64_t prevContainerID = 0;
261    DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
262    Unused << ipcDoc->GetPlatformExtension()->SendPreviousClusterAt(
263        mContainer.AsProxy()->ID(), mOffset, &prevContainerID, &prevOffset);
264    RemoteAccessible* prevContainer = ipcDoc->GetAccessible(prevContainerID);
265    bool moved = prevContainer != mContainer.AsProxy() || prevOffset != mOffset;
266    mContainer = prevContainer;
267    mOffset = prevOffset;
268    return moved;
269  } else if (auto htWrap = ContainerAsHyperTextWrap()) {
270    HyperTextAccessible* prevContainer = nullptr;
271    int32_t prevOffset = 0;
272    htWrap->PreviousClusterAt(mOffset, &prevContainer, &prevOffset);
273    bool moved = prevContainer != htWrap || prevOffset != mOffset;
274    mContainer = prevContainer;
275    mOffset = prevOffset;
276    return moved;
277  }
278
279  return false;
280}
281
282static uint32_t CharacterCount(const AccessibleOrProxy& aContainer) {
283  if (aContainer.IsProxy()) {
284    return aContainer.AsProxy()->CharacterCount();
285  }
286
287  if (aContainer.AsAccessible()->IsHyperText()) {
288    return aContainer.AsAccessible()->AsHyperText()->CharacterCount();
289  }
290
291  return 0;
292}
293
294GeckoTextMarkerRange GeckoTextMarker::Range(EWhichRange aRangeType) {
295  MOZ_ASSERT(!mContainer.IsNull());
296  if (mContainer.IsProxy()) {
297    int32_t startOffset = 0, endOffset = 0;
298    uint64_t startContainerID = 0, endContainerID = 0;
299    DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
300    bool success = ipcDoc->GetPlatformExtension()->SendRangeAt(
301        mContainer.AsProxy()->ID(), mOffset, aRangeType, &startContainerID,
302        &startOffset, &endContainerID, &endOffset);
303    if (success) {
304      return GeckoTextMarkerRange(
305          GeckoTextMarker(ipcDoc->GetAccessible(startContainerID), startOffset),
306          GeckoTextMarker(ipcDoc->GetAccessible(endContainerID), endOffset));
307    }
308  } else if (auto htWrap = ContainerAsHyperTextWrap()) {
309    int32_t startOffset = 0, endOffset = 0;
310    HyperTextAccessible* startContainer = nullptr;
311    HyperTextAccessible* endContainer = nullptr;
312    htWrap->RangeAt(mOffset, aRangeType, &startContainer, &startOffset,
313                    &endContainer, &endOffset);
314    return GeckoTextMarkerRange(GeckoTextMarker(startContainer, startOffset),
315                                GeckoTextMarker(endContainer, endOffset));
316  }
317
318  return GeckoTextMarkerRange(GeckoTextMarker(), GeckoTextMarker());
319}
320
321AccessibleOrProxy GeckoTextMarker::Leaf() {
322  MOZ_ASSERT(!mContainer.IsNull());
323  if (mContainer.IsProxy()) {
324    uint64_t leafID = 0;
325    DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
326    Unused << ipcDoc->GetPlatformExtension()->SendLeafAtOffset(
327        mContainer.AsProxy()->ID(), mOffset, &leafID);
328    return ipcDoc->GetAccessible(leafID);
329  } else if (auto htWrap = ContainerAsHyperTextWrap()) {
330    return htWrap->LeafAtOffset(mOffset);
331  }
332
333  return mContainer;
334}
335
336// GeckoTextMarkerRange
337
338GeckoTextMarkerRange::GeckoTextMarkerRange(
339    AccessibleOrProxy aDoc, AXTextMarkerRangeRef aTextMarkerRange) {
340  if (!aTextMarkerRange ||
341      CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
342    return;
343  }
344
345  AXTextMarkerRef start_marker(
346      AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
347  AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));
348
349  mStart = GeckoTextMarker(aDoc, start_marker);
350  mEnd = GeckoTextMarker(aDoc, end_marker);
351
352  CFRelease(start_marker);
353  CFRelease(end_marker);
354}
355
356GeckoTextMarkerRange::GeckoTextMarkerRange(
357    const AccessibleOrProxy& aAccessible) {
358  if ((aAccessible.IsAccessible() &&
359       aAccessible.AsAccessible()->IsHyperText()) ||
360      (aAccessible.IsProxy() && aAccessible.AsProxy()->IsHyperText())) {
361    // The accessible is a hypertext. Initialize range to its inner text range.
362    mStart = GeckoTextMarker(aAccessible, 0);
363    mEnd = GeckoTextMarker(aAccessible, (CharacterCount(aAccessible)));
364  } else {
365    // The accessible is not a hypertext (maybe a text leaf?). Initialize range
366    // to its offsets in its container.
367    mStart = GeckoTextMarker(aAccessible.Parent(), 0);
368    mEnd = GeckoTextMarker(aAccessible.Parent(), 0);
369    if (mStart.mContainer.IsProxy()) {
370      DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
371      Unused << ipcDoc->GetPlatformExtension()->SendRangeOfChild(
372          mStart.mContainer.AsProxy()->ID(), aAccessible.AsProxy()->ID(),
373          &mStart.mOffset, &mEnd.mOffset);
374    } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
375      htWrap->RangeOfChild(aAccessible.AsAccessible(), &mStart.mOffset,
376                           &mEnd.mOffset);
377    }
378  }
379}
380
381id GeckoTextMarkerRange::CreateAXTextMarkerRange() {
382  if (!IsValid()) {
383    return nil;
384  }
385
386  AXTextMarkerRangeRef cf_text_marker_range =
387      AXTextMarkerRangeCreate(kCFAllocatorDefault, mStart.CreateAXTextMarker(),
388                              mEnd.CreateAXTextMarker());
389  return [static_cast<id>(cf_text_marker_range) autorelease];
390}
391
392NSString* GeckoTextMarkerRange::Text() const {
393  nsAutoString text;
394  if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
395    DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
396    Unused << ipcDoc->GetPlatformExtension()->SendTextForRange(
397        mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
398        mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &text);
399  } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
400    htWrap->TextForRange(text, mStart.mOffset, mEnd.ContainerAsHyperTextWrap(),
401                         mEnd.mOffset);
402  }
403  return nsCocoaUtils::ToNSString(text);
404}
405
406static NSColor* ColorFromColor(const Color& aColor) {
407  return [NSColor colorWithCalibratedRed:NS_GET_R(aColor.mValue) / 255.0
408                                   green:NS_GET_G(aColor.mValue) / 255.0
409                                    blue:NS_GET_B(aColor.mValue) / 255.0
410                                   alpha:1.0];
411}
412
413static NSDictionary* StringAttributesFromAttributes(
414    AccAttributes* aAttributes, const AccessibleOrProxy& aContainer) {
415  NSMutableDictionary* attrDict =
416      [NSMutableDictionary dictionaryWithCapacity:aAttributes->Count()];
417  NSMutableDictionary* fontAttrDict = [[NSMutableDictionary alloc] init];
418  [attrDict setObject:fontAttrDict forKey:@"AXFont"];
419  for (auto iter : *aAttributes) {
420    if (iter.Name() == nsGkAtoms::backgroundColor) {
421      if (Maybe<Color> value = iter.Value<Color>()) {
422        NSColor* color = ColorFromColor(*value);
423        [attrDict setObject:(__bridge id)color.CGColor
424                     forKey:@"AXBackgroundColor"];
425      }
426    } else if (iter.Name() == nsGkAtoms::color) {
427      if (Maybe<Color> value = iter.Value<Color>()) {
428        NSColor* color = ColorFromColor(*value);
429        [attrDict setObject:(__bridge id)color.CGColor
430                     forKey:@"AXForegroundColor"];
431      }
432    } else if (iter.Name() == nsGkAtoms::font_size) {
433      if (Maybe<FontSize> pointSize = iter.Value<FontSize>()) {
434        int32_t fontPixelSize = static_cast<int32_t>(pointSize->mValue * 4 / 3);
435        [fontAttrDict setObject:@(fontPixelSize) forKey:@"AXFontSize"];
436      }
437    } else if (iter.Name() == nsGkAtoms::font_family) {
438      nsAutoString fontFamily;
439      iter.ValueAsString(fontFamily);
440      [fontAttrDict setObject:nsCocoaUtils::ToNSString(fontFamily)
441                       forKey:@"AXFontFamily"];
442    } else if (iter.Name() == nsGkAtoms::textUnderlineColor) {
443      [attrDict setObject:@1 forKey:@"AXUnderline"];
444      if (Maybe<Color> value = iter.Value<Color>()) {
445        NSColor* color = ColorFromColor(*value);
446        [attrDict setObject:(__bridge id)color.CGColor
447                     forKey:@"AXUnderlineColor"];
448      }
449    } else if (iter.Name() == nsGkAtoms::invalid) {
450      // XXX: There is currently no attribute for grammar
451      if (Maybe<nsAtom*> value = iter.Value<nsAtom*>()) {
452        if (*value == nsGkAtoms::spelling) {
453          [attrDict setObject:@YES
454                       forKey:NSAccessibilityMarkedMisspelledTextAttribute];
455        }
456      }
457    } else {
458      nsAutoString valueStr;
459      iter.ValueAsString(valueStr);
460      nsAutoString keyStr;
461      iter.NameAsString(keyStr);
462      [attrDict setObject:nsCocoaUtils::ToNSString(valueStr)
463                   forKey:nsCocoaUtils::ToNSString(keyStr)];
464    }
465  }
466
467  mozAccessible* container = GetNativeFromGeckoAccessible(aContainer);
468  id<MOXAccessible> link =
469      [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
470        return [[moxAcc moxRole] isEqualToString:NSAccessibilityLinkRole];
471      }];
472  if (link) {
473    [attrDict setObject:link forKey:@"AXLink"];
474  }
475
476  id<MOXAccessible> heading =
477      [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
478        return [[moxAcc moxRole] isEqualToString:@"AXHeading"];
479      }];
480  if (heading) {
481    [attrDict setObject:[heading moxValue] forKey:@"AXHeadingLevel"];
482  }
483
484  return attrDict;
485}
486
487NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
488  NSMutableAttributedString* str =
489      [[[NSMutableAttributedString alloc] init] autorelease];
490
491  if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
492    nsTArray<TextAttributesRun> textAttributesRuns;
493    DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
494    Unused << ipcDoc->GetPlatformExtension()->SendAttributedTextForRange(
495        mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
496        mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &textAttributesRuns);
497
498    for (size_t i = 0; i < textAttributesRuns.Length(); i++) {
499      AccAttributes* attributes =
500          textAttributesRuns.ElementAt(i).TextAttributes();
501      RemoteAccessible* container =
502          ipcDoc->GetAccessible(textAttributesRuns.ElementAt(i).ContainerID());
503
504      NSAttributedString* substr = [[[NSAttributedString alloc]
505          initWithString:nsCocoaUtils::ToNSString(
506                             textAttributesRuns.ElementAt(i).Text())
507              attributes:StringAttributesFromAttributes(attributes, container)]
508          autorelease];
509
510      [str appendAttributedString:substr];
511    }
512  } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
513    nsTArray<nsString> texts;
514    nsTArray<LocalAccessible*> containers;
515    nsTArray<RefPtr<AccAttributes>> props;
516
517    htWrap->AttributedTextForRange(texts, props, containers, mStart.mOffset,
518                                   mEnd.ContainerAsHyperTextWrap(),
519                                   mEnd.mOffset);
520
521    MOZ_ASSERT(texts.Length() == props.Length() &&
522               texts.Length() == containers.Length());
523
524    for (size_t i = 0; i < texts.Length(); i++) {
525      NSAttributedString* substr = [[[NSAttributedString alloc]
526          initWithString:nsCocoaUtils::ToNSString(texts.ElementAt(i))
527              attributes:StringAttributesFromAttributes(
528                             props.ElementAt(i), containers.ElementAt(i))]
529          autorelease];
530      [str appendAttributedString:substr];
531    }
532  }
533
534  return str;
535}
536
537int32_t GeckoTextMarkerRange::Length() const {
538  int32_t length = 0;
539  if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
540    DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
541    Unused << ipcDoc->GetPlatformExtension()->SendLengthForRange(
542        mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
543        mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &length);
544  } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
545    length = htWrap->LengthForRange(
546        mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
547  }
548
549  return length;
550}
551
552NSValue* GeckoTextMarkerRange::Bounds() const {
553  nsIntRect rect;
554  if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
555    DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
556    Unused << ipcDoc->GetPlatformExtension()->SendBoundsForRange(
557        mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
558        mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &rect);
559  } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
560    rect = htWrap->BoundsForRange(
561        mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
562  }
563
564  NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
565  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
566  NSRect r =
567      NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor,
568                 [mainView frame].size.height -
569                     static_cast<CGFloat>(rect.y + rect.height) / scaleFactor,
570                 static_cast<CGFloat>(rect.width) / scaleFactor,
571                 static_cast<CGFloat>(rect.height) / scaleFactor);
572
573  return [NSValue valueWithRect:r];
574}
575
576void GeckoTextMarkerRange::Select() const {
577  if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
578    DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
579    Unused << ipcDoc->GetPlatformExtension()->SendSelectRange(
580        mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
581        mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset);
582  } else if (RefPtr<HyperTextAccessibleWrap> htWrap =
583                 mStart.ContainerAsHyperTextWrap()) {
584    RefPtr<HyperTextAccessibleWrap> end = mEnd.ContainerAsHyperTextWrap();
585    htWrap->SelectRange(mStart.mOffset, end, mEnd.mOffset);
586  }
587}
588
589bool GeckoTextMarkerRange::Crop(const AccessibleOrProxy& aContainer) {
590  GeckoTextMarker containerStart(aContainer, 0);
591  GeckoTextMarker containerEnd(aContainer, CharacterCount(aContainer));
592
593  if (mEnd < containerStart || containerEnd < mStart) {
594    // The range ends before the container, or starts after it.
595    return false;
596  }
597
598  if (mStart < containerStart) {
599    // If range start is before container start, adjust range start to
600    // start of container.
601    mStart = containerStart;
602  }
603
604  if (containerEnd < mEnd) {
605    // If range end is after container end, adjust range end to end of
606    // container.
607    mEnd = containerEnd;
608  }
609
610  return true;
611}
612}
613}
614