2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 #import "GeckoTextMarker.h"
12 #include "AccAttributes.h"
13 #include "DocAccessible.h"
14 #include "DocAccessibleParent.h"
15 #include "nsCocoaUtils.h"
16 #include "HyperTextAccessible.h"
18 #include "nsAccUtils.h"
23 struct TextMarkerData {
24 TextMarkerData(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
25 : mDoc(aDoc), mID(aID), mOffset(aOffset) {}
34 GeckoTextMarker::GeckoTextMarker(Accessible* aAcc, int32_t aOffset) {
35 HyperTextAccessibleBase* ht = aAcc->AsHyperTextBase();
36 if (ht && aOffset != nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
37 aOffset <= static_cast<int32_t>(ht->CharacterCount())) {
38 mPoint = aAcc->AsHyperTextBase()->ToTextLeafPoint(aOffset);
40 mPoint = TextLeafPoint(aAcc, aOffset);
44 GeckoTextMarker GeckoTextMarker::MarkerFromAXTextMarker(
45 Accessible* aDoc, AXTextMarkerRef aTextMarker) {
48 return GeckoTextMarker();
51 if (AXTextMarkerGetLength(aTextMarker) != sizeof(TextMarkerData)) {
52 MOZ_ASSERT_UNREACHABLE("Malformed AXTextMarkerRef");
53 return GeckoTextMarker();
56 TextMarkerData markerData;
57 memcpy(&markerData, AXTextMarkerGetBytePtr(aTextMarker),
58 sizeof(TextMarkerData));
60 if (!utils::DocumentExists(aDoc, markerData.mDoc)) {
61 return GeckoTextMarker();
64 Accessible* doc = reinterpret_cast<Accessible*>(markerData.mDoc);
65 MOZ_ASSERT(doc->IsDoc());
66 int32_t offset = markerData.mOffset;
67 Accessible* acc = nullptr;
68 if (doc->IsRemote()) {
69 acc = doc->AsRemote()->AsDoc()->GetAccessible(markerData.mID);
71 acc = doc->AsLocal()->AsDoc()->GetAccessibleByUniqueID(
72 reinterpret_cast<void*>(markerData.mID));
76 return GeckoTextMarker();
79 return GeckoTextMarker(acc, offset);
82 GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
85 TextLeafPoint(aRoot, 0),
86 TextLeafPoint(aRoot, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
87 int32_t index = aIndex;
88 // Iterate through all segments until we exhausted the index sum
89 // so we can find the segment the index lives in.
90 for (TextLeafRange segment : range) {
91 if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
92 // XXX: MacOS expects bullets to be in the range's text, but not in
93 // the calculated length!
97 index -= segment.End().mOffset - segment.Start().mOffset;
99 // The index is in the current segment.
100 return GeckoTextMarker(segment.Start().mAcc,
101 segment.End().mOffset + index);
105 return GeckoTextMarker();
108 AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
113 Accessible* doc = nsAccUtils::DocumentFor(mPoint.mAcc);
114 TextMarkerData markerData(reinterpret_cast<uintptr_t>(doc), mPoint.mAcc->ID(),
116 AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
117 kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&markerData),
118 sizeof(TextMarkerData));
120 return (__bridge AXTextMarkerRef)[(__bridge id)(cf_text_marker)autorelease];
123 bool GeckoTextMarker::Next() {
125 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext,
126 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
128 if (next && next != mPoint) {
136 bool GeckoTextMarker::Previous() {
138 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
139 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
140 if (prev && mPoint != prev) {
149 * Return true if the given point is inside editable content.
151 static bool IsPointInEditable(const TextLeafPoint& aPoint) {
153 if (aPoint.mAcc->State() & states::EDITABLE) {
157 Accessible* parent = aPoint.mAcc->Parent();
158 if (parent && (parent->State() & states::EDITABLE)) {
166 GeckoTextMarkerRange GeckoTextMarker::LeftWordRange() const {
167 bool includeCurrentInStart = !mPoint.IsParagraphStart(true);
168 if (includeCurrentInStart) {
169 TextLeafPoint prevChar =
170 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
171 if (!prevChar.IsSpace()) {
172 includeCurrentInStart = false;
176 TextLeafPoint start = mPoint.FindBoundary(
177 nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
178 includeCurrentInStart
179 ? (TextLeafPoint::BoundaryFlags::eIncludeOrigin |
180 TextLeafPoint::BoundaryFlags::eStopInEditable |
181 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker)
182 : (TextLeafPoint::BoundaryFlags::eStopInEditable |
183 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker));
186 if (start == mPoint) {
187 end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirPrevious,
188 TextLeafPoint::BoundaryFlags::eStopInEditable);
191 if (start != mPoint || end == start) {
192 end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
193 TextLeafPoint::BoundaryFlags::eStopInEditable);
194 if (end < mPoint && IsPointInEditable(end) && !IsPointInEditable(mPoint)) {
200 return GeckoTextMarkerRange(start < end ? start : end,
201 start < end ? end : start);
204 GeckoTextMarkerRange GeckoTextMarker::RightWordRange() const {
205 TextLeafPoint prevChar =
206 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
207 TextLeafPoint::BoundaryFlags::eStopInEditable);
209 if (prevChar != mPoint && mPoint.IsParagraphStart(true)) {
210 return GeckoTextMarkerRange(mPoint, mPoint);
214 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
215 TextLeafPoint::BoundaryFlags::eStopInEditable);
218 // No word to the right of this point.
219 return GeckoTextMarkerRange(mPoint, mPoint);
222 TextLeafPoint start =
223 end.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
224 TextLeafPoint::BoundaryFlags::eStopInEditable);
226 if (start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
227 TextLeafPoint::BoundaryFlags::eStopInEditable) <
229 // Word end is inside of an input to the left of this.
230 return GeckoTextMarkerRange(mPoint, mPoint);
233 if (mPoint < start) {
238 return GeckoTextMarkerRange(start < end ? start : end,
239 start < end ? end : start);
242 GeckoTextMarkerRange GeckoTextMarker::LineRange() const {
243 TextLeafPoint start = mPoint.FindBoundary(
244 nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
245 TextLeafPoint::BoundaryFlags::eStopInEditable |
246 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker |
247 TextLeafPoint::BoundaryFlags::eIncludeOrigin);
248 // If this is a blank line containing only a line feed, the start boundary
249 // is the same as the end boundary. We do not want to walk to the end of the
252 start.IsLineFeedChar()
254 : start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
255 TextLeafPoint::BoundaryFlags::eStopInEditable);
257 return GeckoTextMarkerRange(start, end);
260 GeckoTextMarkerRange GeckoTextMarker::LeftLineRange() const {
261 TextLeafPoint start = mPoint.FindBoundary(
262 nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
263 TextLeafPoint::BoundaryFlags::eStopInEditable |
264 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
266 start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
267 TextLeafPoint::BoundaryFlags::eStopInEditable);
269 return GeckoTextMarkerRange(start, end);
272 GeckoTextMarkerRange GeckoTextMarker::RightLineRange() const {
274 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
275 TextLeafPoint::BoundaryFlags::eStopInEditable);
276 TextLeafPoint start =
277 end.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
278 TextLeafPoint::BoundaryFlags::eStopInEditable);
280 return GeckoTextMarkerRange(start, end);
283 GeckoTextMarkerRange GeckoTextMarker::ParagraphRange() const {
284 // XXX: WebKit gets trapped in inputs. Maybe we shouldn't?
286 mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirNext,
287 TextLeafPoint::BoundaryFlags::eStopInEditable);
288 TextLeafPoint start =
289 end.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirPrevious,
290 TextLeafPoint::BoundaryFlags::eStopInEditable);
292 return GeckoTextMarkerRange(start, end);
295 GeckoTextMarkerRange GeckoTextMarker::StyleRange() const {
296 if (mPoint.mOffset == 0) {
297 // If the marker is on the boundary between two leafs, MacOS expects the
299 TextLeafPoint prev = mPoint.FindBoundary(
300 nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
301 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
302 if (prev != mPoint) {
303 return GeckoTextMarker(prev).StyleRange();
307 TextLeafPoint start(mPoint.mAcc, 0);
308 TextLeafPoint end(mPoint.mAcc, nsAccUtils::TextLength(mPoint.mAcc));
309 return GeckoTextMarkerRange(start, end);
312 Accessible* GeckoTextMarker::Leaf() {
313 MOZ_ASSERT(mPoint.mAcc);
314 Accessible* acc = mPoint.mAcc;
315 if (mPoint.mOffset == 0) {
316 // If the marker is on the boundary between two leafs, MacOS expects the
318 TextLeafPoint prev = mPoint.FindBoundary(
319 nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
320 TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
324 Accessible* parent = acc->Parent();
325 return parent && nsAccUtils::MustPrune(parent) ? parent : acc;
328 // GeckoTextMarkerRange
330 GeckoTextMarkerRange::GeckoTextMarkerRange(Accessible* aAccessible) {
331 mRange = TextLeafRange(
332 TextLeafPoint(aAccessible, 0),
333 TextLeafPoint(aAccessible, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
336 GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
337 Accessible* aDoc, AXTextMarkerRangeRef aTextMarkerRange) {
338 if (!aTextMarkerRange ||
339 CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
340 return GeckoTextMarkerRange();
343 AXTextMarkerRef start_marker(
344 AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
345 AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));
347 GeckoTextMarker start =
348 GeckoTextMarker::MarkerFromAXTextMarker(aDoc, start_marker);
349 GeckoTextMarker end =
350 GeckoTextMarker::MarkerFromAXTextMarker(aDoc, end_marker);
352 CFRelease(start_marker);
353 CFRelease(end_marker);
355 return GeckoTextMarkerRange(start, end);
358 AXTextMarkerRangeRef GeckoTextMarkerRange::CreateAXTextMarkerRange() {
363 GeckoTextMarker start = GeckoTextMarker(mRange.Start());
364 GeckoTextMarker end = GeckoTextMarker(mRange.End());
366 AXTextMarkerRangeRef cf_text_marker_range =
367 AXTextMarkerRangeCreate(kCFAllocatorDefault, start.CreateAXTextMarker(),
368 end.CreateAXTextMarker());
370 return (__bridge AXTextMarkerRangeRef)[(__bridge id)(
371 cf_text_marker_range)autorelease];
374 NSString* GeckoTextMarkerRange::Text() const {
375 if (mRange.Start() == mRange.End()) {
379 if ((mRange.Start().mAcc == mRange.End().mAcc) &&
380 (mRange.Start().mAcc->ChildCount() == 0) &&
381 (mRange.Start().mAcc->State() & states::EDITABLE)) {
386 TextLeafPoint prev = mRange.Start().FindBoundary(
387 nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
388 TextLeafRange range =
389 prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
390 ? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
393 for (TextLeafRange segment : range) {
394 TextLeafPoint start = segment.Start();
395 if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
399 start.mAcc->AppendTextTo(text, start.mOffset,
400 segment.End().mOffset - start.mOffset);
403 return nsCocoaUtils::ToNSString(text);
406 static void AppendTextToAttributedString(
407 NSMutableAttributedString* aAttributedString, Accessible* aAccessible,
408 const nsString& aString, AccAttributes* aAttributes) {
409 NSAttributedString* substr = [[[NSAttributedString alloc]
410 initWithString:nsCocoaUtils::ToNSString(aString)
411 attributes:utils::StringAttributesFromAccAttributes(
412 aAttributes, aAccessible)] autorelease];
414 [aAttributedString appendAttributedString:substr];
417 NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
418 NSMutableAttributedString* str =
419 [[[NSMutableAttributedString alloc] init] autorelease];
421 if (mRange.Start() == mRange.End()) {
425 if ((mRange.Start().mAcc == mRange.End().mAcc) &&
426 (mRange.Start().mAcc->ChildCount() == 0) &&
427 (mRange.Start().mAcc->IsTextField())) {
431 TextLeafPoint prev = mRange.Start().FindBoundary(
432 nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
433 TextLeafRange range =
434 prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
435 ? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
439 RefPtr<AccAttributes> currentRun = range.Start().GetTextAttributes();
440 Accessible* runAcc = range.Start().mAcc;
441 for (TextLeafRange segment : range) {
442 TextLeafPoint start = segment.Start();
443 TextLeafPoint attributesNext;
445 if (start.mAcc->IsText()) {
446 attributesNext = start.FindTextAttrsStart(eDirNext, false);
448 // If this segment isn't a text leaf, but another kind of inline element
449 // like a control, just consider this full segment one "attributes run".
450 attributesNext = segment.End();
452 if (attributesNext == start) {
453 // XXX: FindTextAttrsStart should not return the same point.
456 RefPtr<AccAttributes> attributes = start.GetTextAttributes();
457 if (!currentRun || !attributes || !attributes->Equal(currentRun)) {
458 // If currentRun is null this is a non-text control and we will
459 // append a run with no text or attributes, just an AXAttachment
460 // referencing this accessible.
461 AppendTextToAttributedString(str, runAcc, text, currentRun);
463 currentRun = attributes;
467 attributesNext < segment.End() ? attributesNext : segment.End();
468 start.mAcc->AppendTextTo(text, start.mOffset,
469 end.mOffset - start.mOffset);
470 start = attributesNext;
472 } while (attributesNext < segment.End());
475 if (!text.IsEmpty()) {
476 AppendTextToAttributedString(str, runAcc, text, currentRun);
482 int32_t GeckoTextMarkerRange::Length() const {
484 for (TextLeafRange segment : mRange) {
485 if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
486 // XXX: MacOS expects bullets to be in the range's text, but not in
487 // the calculated length!
490 length += segment.End().mOffset - segment.Start().mOffset;
496 NSValue* GeckoTextMarkerRange::Bounds() const {
497 LayoutDeviceIntRect rect = mRange ? mRange.Bounds() : LayoutDeviceIntRect();
499 NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
500 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
502 NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor,
503 [mainView frame].size.height -
504 static_cast<CGFloat>(rect.y + rect.height) / scaleFactor,
505 static_cast<CGFloat>(rect.width) / scaleFactor,
506 static_cast<CGFloat>(rect.height) / scaleFactor);
508 return [NSValue valueWithRect:r];
511 void GeckoTextMarkerRange::Select() const { mRange.SetSelection(0); }
514 } // namespace mozilla