Bug 1648429 [wpt PR 24338] - Python 3: port tests in service-workers [part 5], a...
[gecko.git] / accessible / mac / GeckoTextMarker.mm
blob8007ce917c6a50fcdf80edbb9e0b5a88898a677a
1 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DocAccessibleParent.h"
8 #include "AccessibleOrProxy.h"
9 #include "nsCocoaUtils.h"
11 #import "GeckoTextMarker.h"
13 extern "C" {
15 CFTypeID AXTextMarkerGetTypeID();
17 AXTextMarkerRef AXTextMarkerCreate(CFAllocatorRef allocator, const UInt8* bytes, CFIndex length);
19 const UInt8* AXTextMarkerGetBytePtr(AXTextMarkerRef text_marker);
21 size_t AXTextMarkerGetLength(AXTextMarkerRef text_marker);
23 CFTypeID AXTextMarkerRangeGetTypeID();
25 AXTextMarkerRangeRef AXTextMarkerRangeCreate(CFAllocatorRef allocator, AXTextMarkerRef start_marker,
26                                              AXTextMarkerRef end_marker);
28 AXTextMarkerRef AXTextMarkerRangeCopyStartMarker(AXTextMarkerRangeRef text_marker_range);
30 AXTextMarkerRef AXTextMarkerRangeCopyEndMarker(AXTextMarkerRangeRef text_marker_range);
33 namespace mozilla {
34 namespace a11y {
36 struct OpaqueGeckoTextMarker {
37   OpaqueGeckoTextMarker(uintptr_t aID, int32_t aOffset) : mID(aID), mOffset(aOffset) {}
38   OpaqueGeckoTextMarker() {}
39   uintptr_t mID;
40   int32_t mOffset;
43 // GeckoTextMarker
45 GeckoTextMarker::GeckoTextMarker(AccessibleOrProxy aDoc, AXTextMarkerRef aTextMarker) {
46   MOZ_ASSERT(!aDoc.IsNull());
47   OpaqueGeckoTextMarker opaqueMarker;
48   if (AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
49     memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker), sizeof(OpaqueGeckoTextMarker));
50     if (aDoc.IsProxy()) {
51       mContainer = aDoc.AsProxy()->AsDoc()->GetAccessible(opaqueMarker.mID);
52     } else {
53       mContainer = aDoc.AsAccessible()->AsDoc()->GetAccessibleByUniqueID(
54           reinterpret_cast<void*>(opaqueMarker.mID));
55     }
57     mOffset = opaqueMarker.mOffset;
58   }
61 id GeckoTextMarker::CreateAXTextMarker() {
62   uintptr_t identifier = mContainer.IsProxy()
63                              ? mContainer.AsProxy()->ID()
64                              : reinterpret_cast<uintptr_t>(mContainer.AsAccessible()->UniqueID());
65   OpaqueGeckoTextMarker opaqueMarker(identifier, mOffset);
66   AXTextMarkerRef cf_text_marker =
67       AXTextMarkerCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&opaqueMarker),
68                          sizeof(OpaqueGeckoTextMarker));
70   return [static_cast<id>(cf_text_marker) autorelease];
73 bool GeckoTextMarker::operator<(const GeckoTextMarker& aPoint) const {
74   if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
76   // Build the chain of parents
77   AccessibleOrProxy p1 = mContainer;
78   AccessibleOrProxy p2 = aPoint.mContainer;
79   AutoTArray<AccessibleOrProxy, 30> parents1, parents2;
80   do {
81     parents1.AppendElement(p1);
82     p1 = p1.Parent();
83   } while (!p1.IsNull());
84   do {
85     parents2.AppendElement(p2);
86     p2 = p2.Parent();
87   } while (!p2.IsNull());
89   // Find where the parent chain differs
90   uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
91   for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
92     AccessibleOrProxy child1 = parents1.ElementAt(--pos1);
93     AccessibleOrProxy child2 = parents2.ElementAt(--pos2);
94     if (child1 != child2) {
95       return child1.IndexInParent() < child2.IndexInParent();
96     }
97   }
99   MOZ_ASSERT_UNREACHABLE("Broken tree?!");
100   return false;
103 // GeckoTextMarkerRange
105 GeckoTextMarkerRange::GeckoTextMarkerRange(AccessibleOrProxy aDoc,
106                                            AXTextMarkerRangeRef aTextMarkerRange) {
107   if (CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
108     return;
109   }
111   AXTextMarkerRef start_marker(AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
112   AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));
114   mStart = GeckoTextMarker(aDoc, start_marker);
115   mEnd = GeckoTextMarker(aDoc, end_marker);
117   CFRelease(start_marker);
118   CFRelease(end_marker);
121 id GeckoTextMarkerRange::CreateAXTextMarkerRange() {
122   AXTextMarkerRangeRef cf_text_marker_range = AXTextMarkerRangeCreate(
123       kCFAllocatorDefault, mStart.CreateAXTextMarker(), mEnd.CreateAXTextMarker());
124   return [static_cast<id>(cf_text_marker_range) autorelease];
127 NSString* GeckoTextMarkerRange::Text() const {
128   nsAutoString text;
129   TextInternal(text, mStart.mContainer, mStart.mOffset);
130   return nsCocoaUtils::ToNSString(text);
133 void GeckoTextMarkerRange::AppendTextTo(const AccessibleOrProxy& aContainer, nsAString& aText,
134                                         uint32_t aStartOffset, uint32_t aEndOffset) const {
135   nsAutoString text;
136   if (aContainer.IsProxy()) {
137     aContainer.AsProxy()->TextSubstring(aStartOffset, aEndOffset, text);
138   } else if (aContainer.AsAccessible()->IsHyperText()) {
139     aContainer.AsAccessible()->AsHyperText()->TextSubstring(aStartOffset, aEndOffset, text);
140   }
142   aText.Append(text);
145 int32_t GeckoTextMarkerRange::StartOffset(const AccessibleOrProxy& aChild) const {
146   if (aChild.IsProxy()) {
147     bool unused;
148     return aChild.AsProxy()->StartOffset(&unused);
149   }
151   return aChild.AsAccessible()->StartOffset();
154 int32_t GeckoTextMarkerRange::EndOffset(const AccessibleOrProxy& aChild) const {
155   if (aChild.IsProxy()) {
156     bool unused;
157     return aChild.AsProxy()->EndOffset(&unused);
158   }
160   return aChild.AsAccessible()->EndOffset();
163 int32_t GeckoTextMarkerRange::LinkCount(const AccessibleOrProxy& aContainer) const {
164   if (aContainer.IsProxy()) {
165     return aContainer.AsProxy()->LinkCount();
166   }
168   if (aContainer.AsAccessible()->IsHyperText()) {
169     return aContainer.AsAccessible()->AsHyperText()->LinkCount();
170   }
172   return 0;
175 AccessibleOrProxy GeckoTextMarkerRange::LinkAt(const AccessibleOrProxy& aContainer,
176                                                uint32_t aIndex) const {
177   if (aContainer.IsProxy()) {
178     return aContainer.AsProxy()->LinkAt(aIndex);
179   }
181   if (aContainer.AsAccessible()->IsHyperText()) {
182     return aContainer.AsAccessible()->AsHyperText()->LinkAt(aIndex);
183   }
185   return AccessibleOrProxy();
188 bool GeckoTextMarkerRange::TextInternal(nsAString& aText, AccessibleOrProxy aCurrent,
189                                         int32_t aStartIntlOffset) const {
190   int32_t endIntlOffset = aCurrent == mEnd.mContainer ? mEnd.mOffset : -1;
191   if (endIntlOffset == 0) {
192     return false;
193   }
195   AccessibleOrProxy next;
196   int32_t linkCount = LinkCount(aCurrent);
197   int32_t linkStartOffset = 0;
198   int32_t end = endIntlOffset;
199   // Find the first link that is at or after the start offset.
200   for (int32_t i = 0; i < linkCount; i++) {
201     AccessibleOrProxy link = LinkAt(aCurrent, i);
202     linkStartOffset = StartOffset(link);
203     if (aStartIntlOffset <= linkStartOffset) {
204       next = link;
205       break;
206     }
207   }
209   bool endIsBeyondLink = !next.IsNull() && (endIntlOffset < 0 || endIntlOffset > linkStartOffset);
211   if (endIsBeyondLink) {
212     end = linkStartOffset;
213   }
215   AppendTextTo(aCurrent, aText, aStartIntlOffset, end);
217   if (endIsBeyondLink) {
218     if (!TextInternal(aText, next, 0)) {
219       return false;
220     }
221   }
223   if (endIntlOffset >= 0) {
224     // If our original end marker is positive, we know we found all the text we
225     // needed within this current node, and our search is complete.
226     return false;
227   }
229   next = aCurrent.Parent();
230   if (!next.IsNull()) {
231     // The end offset is passed this link, go back to the parent
232     // and continue from this link's end offset.
233     return TextInternal(aText, next, EndOffset(aCurrent));
234   }
236   return true;