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 <Cocoa/Cocoa.h>
9 #include "DocAccessible.h"
11 #import "MOXTextMarkerDelegate.h"
13 #include "mozAccessible.h"
14 #include "mozilla/Preferences.h"
15 #include "nsISelectionListener.h"
17 using namespace mozilla::a11y;
19 #define PREF_ACCESSIBILITY_MAC_DEBUG "accessibility.mac.debug"
21 static nsTHashMap<nsPtrHashKey<mozilla::a11y::Accessible>,
22 MOXTextMarkerDelegate*>
25 @implementation MOXTextMarkerDelegate
27 + (id)getOrCreateForDoc:(mozilla::a11y::Accessible*)aDoc {
30 MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc);
32 delegate = [[MOXTextMarkerDelegate alloc] initWithDoc:aDoc];
33 sDelegates.InsertOrUpdate(aDoc, delegate);
40 + (void)destroyForDoc:(mozilla::a11y::Accessible*)aDoc {
43 MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc);
45 sDelegates.Remove(aDoc);
50 - (id)initWithDoc:(Accessible*)aDoc {
51 MOZ_ASSERT(aDoc, "Cannot init MOXTextDelegate with null");
52 if ((self = [super init])) {
53 mGeckoDocAccessible = aDoc;
56 mCaretMoveGranularity = nsISelectionListener::NO_AMOUNT;
62 [self invalidateSelection];
66 - (void)setSelectionFrom:(Accessible*)startContainer
67 at:(int32_t)startOffset
68 to:(Accessible*)endContainer
69 at:(int32_t)endOffset {
70 GeckoTextMarkerRange selection(GeckoTextMarker(startContainer, startOffset),
71 GeckoTextMarker(endContainer, endOffset));
73 // We store it as an AXTextMarkerRange because it is a safe
74 // way to keep a weak reference - when we need to use the
75 // range we can convert it back to a GeckoTextMarkerRange
76 // and check that it's valid.
77 mSelection = selection.CreateAXTextMarkerRange();
81 - (void)setCaretOffset:(mozilla::a11y::Accessible*)container
83 moveGranularity:(int32_t)granularity {
84 GeckoTextMarker caretMarker(container, offset);
87 mCaret = caretMarker.CreateAXTextMarker();
88 mCaretMoveGranularity = granularity;
93 mozAccessible* GetEditableNativeFromGeckoAccessible(Accessible* aAcc) {
94 // The gecko accessible may not have a native accessible so we need
95 // to walk up the parent chain to find the nearest one.
96 // This happens when caching is enabled and the text marker's accessible
97 // may be a text leaf that is pruned from the platform.
98 for (Accessible* acc = aAcc; acc; acc = acc->Parent()) {
99 if (mozAccessible* mozAcc = GetNativeFromGeckoAccessible(acc)) {
100 return [mozAcc moxEditableAncestor];
107 // This returns an info object to pass with AX SelectedTextChanged events.
108 // It uses the current and previous caret position to make decisions
109 // regarding which attributes to add to the info object.
110 - (NSDictionary*)selectionChangeInfo {
111 GeckoTextMarkerRange selectedGeckoRange =
112 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
113 mGeckoDocAccessible, mSelection);
115 int32_t stateChangeType =
116 selectedGeckoRange.Start() == selectedGeckoRange.End()
117 ? AXTextStateChangeTypeSelectionMove
118 : AXTextStateChangeTypeSelectionExtend;
120 // This is the base info object, includes the selected marker range and
121 // the change type depending on the collapsed state of the selection.
122 NSMutableDictionary* info = [[@{
123 @"AXSelectedTextMarkerRange" : selectedGeckoRange.IsValid()
124 ? (__bridge id)mSelection
126 @"AXTextStateChangeType" : @(stateChangeType),
127 } mutableCopy] autorelease];
129 GeckoTextMarker caretMarker =
130 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, mCaret);
131 GeckoTextMarker prevCaretMarker =
132 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, mPrevCaret);
134 if (!caretMarker.IsValid()) {
135 // If the current caret is invalid, stop here and return base info.
139 mozAccessible* caretEditable =
140 GetEditableNativeFromGeckoAccessible(caretMarker.Acc());
142 if (!caretEditable && stateChangeType == AXTextStateChangeTypeSelectionMove) {
143 // If we are not in an editable, VO expects AXTextStateSync to be present
145 info[@"AXTextStateSync"] = @YES;
148 if (!prevCaretMarker.IsValid() || caretMarker == prevCaretMarker) {
149 // If we have no stored previous marker, stop here.
153 mozAccessible* prevCaretEditable =
154 GetEditableNativeFromGeckoAccessible(prevCaretMarker.Acc());
156 if (prevCaretEditable != caretEditable) {
157 // If the caret goes in or out of an editable, consider the
158 // move direction "discontiguous".
159 info[@"AXTextSelectionDirection"] =
160 @(AXTextSelectionDirectionDiscontiguous);
161 if ([[caretEditable moxFocused] boolValue]) {
162 // If the caret is in a new focused editable, VO expects this attribute to
163 // be present and to be true.
164 info[@"AXTextSelectionChangedFocus"] = @YES;
170 bool isForward = prevCaretMarker < caretMarker;
171 int direction = isForward ? AXTextSelectionDirectionNext
172 : AXTextSelectionDirectionPrevious;
174 int32_t granularity = AXTextSelectionGranularityUnknown;
175 switch (mCaretMoveGranularity) {
176 case nsISelectionListener::CHARACTER_AMOUNT:
177 case nsISelectionListener::CLUSTER_AMOUNT:
178 granularity = AXTextSelectionGranularityCharacter;
180 case nsISelectionListener::WORD_AMOUNT:
181 case nsISelectionListener::WORDNOSPACE_AMOUNT:
182 granularity = AXTextSelectionGranularityWord;
184 case nsISelectionListener::LINE_AMOUNT:
185 granularity = AXTextSelectionGranularityLine;
187 case nsISelectionListener::BEGINLINE_AMOUNT:
188 direction = AXTextSelectionDirectionBeginning;
189 granularity = AXTextSelectionGranularityLine;
191 case nsISelectionListener::ENDLINE_AMOUNT:
192 direction = AXTextSelectionDirectionEnd;
193 granularity = AXTextSelectionGranularityLine;
195 case nsISelectionListener::PARAGRAPH_AMOUNT:
196 granularity = AXTextSelectionGranularityParagraph;
202 // Determine selection direction with marker comparison.
203 // If the delta between the two markers is more than one, consider it
204 // a word. Not accurate, but good enough for VO.
205 [info addEntriesFromDictionary:@{
206 @"AXTextSelectionDirection" : @(direction),
207 @"AXTextSelectionGranularity" : @(granularity)
213 - (void)invalidateSelection {
214 CFRelease(mSelection);
216 CFRelease(mPrevCaret);
220 - (mozilla::a11y::GeckoTextMarkerRange)selection {
221 return mozilla::a11y::GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
222 mGeckoDocAccessible, mSelection);
225 - (AXTextMarkerRef)moxStartTextMarker {
226 GeckoTextMarker geckoTextPoint(mGeckoDocAccessible, 0);
227 return geckoTextPoint.CreateAXTextMarker();
230 - (AXTextMarkerRef)moxEndTextMarker {
231 GeckoTextMarker geckoTextPoint(mGeckoDocAccessible,
232 nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
233 return geckoTextPoint.CreateAXTextMarker();
236 - (AXTextMarkerRangeRef)moxSelectedTextMarkerRange {
237 return mSelection && GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
238 mGeckoDocAccessible, mSelection)
244 - (NSString*)moxStringForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
245 mozilla::a11y::GeckoTextMarkerRange range =
246 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
247 mGeckoDocAccessible, textMarkerRange);
248 if (!range.IsValid()) {
255 - (NSNumber*)moxLengthForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
256 mozilla::a11y::GeckoTextMarkerRange range =
257 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
258 mGeckoDocAccessible, textMarkerRange);
259 if (!range.IsValid()) {
263 return @(range.Length());
266 - (AXTextMarkerRangeRef)moxTextMarkerRangeForUnorderedTextMarkers:
267 (NSArray*)textMarkers {
268 if ([textMarkers count] != 2) {
269 // Don't allow anything but a two member array.
273 GeckoTextMarker p1 = GeckoTextMarker::MarkerFromAXTextMarker(
274 mGeckoDocAccessible, (__bridge AXTextMarkerRef)textMarkers[0]);
275 GeckoTextMarker p2 = GeckoTextMarker::MarkerFromAXTextMarker(
276 mGeckoDocAccessible, (__bridge AXTextMarkerRef)textMarkers[1]);
278 if (!p1.IsValid() || !p2.IsValid()) {
279 // If either marker is invalid, return nil.
283 bool ordered = p1 < p2;
284 GeckoTextMarkerRange range(ordered ? p1 : p2, ordered ? p2 : p1);
286 return range.CreateAXTextMarkerRange();
289 - (AXTextMarkerRef)moxStartTextMarkerForTextMarkerRange:
290 (AXTextMarkerRangeRef)textMarkerRange {
291 mozilla::a11y::GeckoTextMarkerRange range =
292 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
293 mGeckoDocAccessible, textMarkerRange);
295 return range.IsValid() ? range.Start().CreateAXTextMarker() : nil;
298 - (AXTextMarkerRef)moxEndTextMarkerForTextMarkerRange:
299 (AXTextMarkerRangeRef)textMarkerRange {
300 mozilla::a11y::GeckoTextMarkerRange range =
301 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
302 mGeckoDocAccessible, textMarkerRange);
304 return range.IsValid() ? range.End().CreateAXTextMarker() : nil;
307 - (AXTextMarkerRangeRef)moxLeftWordTextMarkerRangeForTextMarker:
308 (AXTextMarkerRef)textMarker {
309 GeckoTextMarker geckoTextMarker =
310 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
311 if (!geckoTextMarker.IsValid()) {
315 return geckoTextMarker.LeftWordRange().CreateAXTextMarkerRange();
318 - (AXTextMarkerRangeRef)moxRightWordTextMarkerRangeForTextMarker:
319 (AXTextMarkerRef)textMarker {
320 GeckoTextMarker geckoTextMarker =
321 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
322 if (!geckoTextMarker.IsValid()) {
326 return geckoTextMarker.RightWordRange().CreateAXTextMarkerRange();
329 - (AXTextMarkerRangeRef)moxLineTextMarkerRangeForTextMarker:
330 (AXTextMarkerRef)textMarker {
331 GeckoTextMarker geckoTextMarker =
332 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
333 if (!geckoTextMarker.IsValid()) {
337 return geckoTextMarker.LineRange().CreateAXTextMarkerRange();
340 - (AXTextMarkerRangeRef)moxLeftLineTextMarkerRangeForTextMarker:
341 (AXTextMarkerRef)textMarker {
342 GeckoTextMarker geckoTextMarker =
343 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
344 if (!geckoTextMarker.IsValid()) {
348 return geckoTextMarker.LeftLineRange().CreateAXTextMarkerRange();
351 - (AXTextMarkerRangeRef)moxRightLineTextMarkerRangeForTextMarker:
352 (AXTextMarkerRef)textMarker {
353 GeckoTextMarker geckoTextMarker =
354 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
355 if (!geckoTextMarker.IsValid()) {
359 return geckoTextMarker.RightLineRange().CreateAXTextMarkerRange();
362 - (AXTextMarkerRangeRef)moxParagraphTextMarkerRangeForTextMarker:
363 (AXTextMarkerRef)textMarker {
364 GeckoTextMarker geckoTextMarker =
365 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
366 if (!geckoTextMarker.IsValid()) {
370 return geckoTextMarker.ParagraphRange().CreateAXTextMarkerRange();
374 - (AXTextMarkerRangeRef)moxStyleTextMarkerRangeForTextMarker:
375 (AXTextMarkerRef)textMarker {
376 GeckoTextMarker geckoTextMarker =
377 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
378 if (!geckoTextMarker.IsValid()) {
382 return geckoTextMarker.StyleRange().CreateAXTextMarkerRange();
385 - (AXTextMarkerRef)moxNextTextMarkerForTextMarker:(AXTextMarkerRef)textMarker {
386 GeckoTextMarker geckoTextMarker =
387 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
388 if (!geckoTextMarker.IsValid()) {
392 if (!geckoTextMarker.Next()) {
396 return geckoTextMarker.CreateAXTextMarker();
399 - (AXTextMarkerRef)moxPreviousTextMarkerForTextMarker:
400 (AXTextMarkerRef)textMarker {
401 GeckoTextMarker geckoTextMarker =
402 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
403 if (!geckoTextMarker.IsValid()) {
407 if (!geckoTextMarker.Previous()) {
411 return geckoTextMarker.CreateAXTextMarker();
414 - (NSAttributedString*)moxAttributedStringForTextMarkerRange:
415 (AXTextMarkerRangeRef)textMarkerRange {
416 mozilla::a11y::GeckoTextMarkerRange range =
417 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
418 mGeckoDocAccessible, textMarkerRange);
419 if (!range.IsValid()) {
423 return range.AttributedText();
426 - (NSValue*)moxBoundsForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
427 mozilla::a11y::GeckoTextMarkerRange range =
428 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
429 mGeckoDocAccessible, textMarkerRange);
430 if (!range.IsValid()) {
434 return range.Bounds();
437 - (NSNumber*)moxIndexForTextMarker:(AXTextMarkerRef)textMarker {
438 GeckoTextMarker geckoTextMarker =
439 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
440 if (!geckoTextMarker.IsValid()) {
444 GeckoTextMarkerRange range(GeckoTextMarker(mGeckoDocAccessible, 0),
447 return @(range.Length());
450 - (AXTextMarkerRef)moxTextMarkerForIndex:(NSNumber*)index {
451 GeckoTextMarker geckoTextMarker = GeckoTextMarker::MarkerFromIndex(
452 mGeckoDocAccessible, [index integerValue]);
453 if (!geckoTextMarker.IsValid()) {
457 return geckoTextMarker.CreateAXTextMarker();
460 - (id)moxUIElementForTextMarker:(AXTextMarkerRef)textMarker {
461 GeckoTextMarker geckoTextMarker =
462 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
463 if (!geckoTextMarker.IsValid()) {
467 Accessible* leaf = geckoTextMarker.Leaf();
472 return GetNativeFromGeckoAccessible(leaf);
475 - (AXTextMarkerRangeRef)moxTextMarkerRangeForUIElement:(id)element {
476 if (![element isKindOfClass:[mozAccessible class]]) {
480 GeckoTextMarkerRange range((Accessible*)[element geckoAccessible]);
481 return range.CreateAXTextMarkerRange();
484 - (NSString*)moxMozDebugDescriptionForTextMarker:(AXTextMarkerRef)textMarker {
485 if (!mozilla::Preferences::GetBool(PREF_ACCESSIBILITY_MAC_DEBUG)) {
489 GeckoTextMarker geckoTextMarker =
490 GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
491 if (!geckoTextMarker.IsValid()) {
492 return @"<GeckoTextMarker 0x0 [0]>";
495 return [NSString stringWithFormat:@"<GeckoTextMarker %p [%d]>",
496 geckoTextMarker.Acc(),
497 geckoTextMarker.Offset()];
500 - (NSString*)moxMozDebugDescriptionForTextMarkerRange:
501 (AXTextMarkerRangeRef)textMarkerRange {
502 if (!mozilla::Preferences::GetBool(PREF_ACCESSIBILITY_MAC_DEBUG)) {
506 mozilla::a11y::GeckoTextMarkerRange range =
507 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
508 mGeckoDocAccessible, textMarkerRange);
509 if (!range.IsValid()) {
510 return @"<GeckoTextMarkerRange 0x0 [0] - 0x0 [0]>";
513 return [NSString stringWithFormat:@"<GeckoTextMarkerRange %p [%d] - %p [%d]>",
514 range.Start().Acc(), range.Start().Offset(),
515 range.End().Acc(), range.End().Offset()];
518 - (void)moxSetSelectedTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
519 mozilla::a11y::GeckoTextMarkerRange range =
520 GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
521 mGeckoDocAccessible, textMarkerRange);
522 if (range.IsValid()) {