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"
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);
36 struct OpaqueGeckoTextMarker {
37 OpaqueGeckoTextMarker(uintptr_t aID, int32_t aOffset) : mID(aID), mOffset(aOffset) {}
38 OpaqueGeckoTextMarker() {}
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));
51 mContainer = aDoc.AsProxy()->AsDoc()->GetAccessible(opaqueMarker.mID);
53 mContainer = aDoc.AsAccessible()->AsDoc()->GetAccessibleByUniqueID(
54 reinterpret_cast<void*>(opaqueMarker.mID));
57 mOffset = opaqueMarker.mOffset;
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;
81 parents1.AppendElement(p1);
83 } while (!p1.IsNull());
85 parents2.AppendElement(p2);
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();
99 MOZ_ASSERT_UNREACHABLE("Broken tree?!");
103 // GeckoTextMarkerRange
105 GeckoTextMarkerRange::GeckoTextMarkerRange(AccessibleOrProxy aDoc,
106 AXTextMarkerRangeRef aTextMarkerRange) {
107 if (CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
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 {
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 {
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);
145 int32_t GeckoTextMarkerRange::StartOffset(const AccessibleOrProxy& aChild) const {
146 if (aChild.IsProxy()) {
148 return aChild.AsProxy()->StartOffset(&unused);
151 return aChild.AsAccessible()->StartOffset();
154 int32_t GeckoTextMarkerRange::EndOffset(const AccessibleOrProxy& aChild) const {
155 if (aChild.IsProxy()) {
157 return aChild.AsProxy()->EndOffset(&unused);
160 return aChild.AsAccessible()->EndOffset();
163 int32_t GeckoTextMarkerRange::LinkCount(const AccessibleOrProxy& aContainer) const {
164 if (aContainer.IsProxy()) {
165 return aContainer.AsProxy()->LinkCount();
168 if (aContainer.AsAccessible()->IsHyperText()) {
169 return aContainer.AsAccessible()->AsHyperText()->LinkCount();
175 AccessibleOrProxy GeckoTextMarkerRange::LinkAt(const AccessibleOrProxy& aContainer,
176 uint32_t aIndex) const {
177 if (aContainer.IsProxy()) {
178 return aContainer.AsProxy()->LinkAt(aIndex);
181 if (aContainer.AsAccessible()->IsHyperText()) {
182 return aContainer.AsAccessible()->AsHyperText()->LinkAt(aIndex);
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) {
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) {
209 bool endIsBeyondLink = !next.IsNull() && (endIntlOffset < 0 || endIntlOffset > linkStartOffset);
211 if (endIsBeyondLink) {
212 end = linkStartOffset;
215 AppendTextTo(aCurrent, aText, aStartIntlOffset, end);
217 if (endIsBeyondLink) {
218 if (!TextInternal(aText, next, 0)) {
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.
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));