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