tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MOXTextMarkerDelegate.mm (16606B)


      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/. */
      7 
      8 #import <Cocoa/Cocoa.h>
      9 #include "DocAccessible.h"
     10 
     11 #import "MOXTextMarkerDelegate.h"
     12 
     13 #include "mozAccessible.h"
     14 #include "mozilla/Preferences.h"
     15 #include "nsISelectionListener.h"
     16 
     17 using namespace mozilla::a11y;
     18 
     19 #define PREF_ACCESSIBILITY_MAC_DEBUG "accessibility.mac.debug"
     20 
     21 MOZ_RUNINIT static nsTHashMap<nsPtrHashKey<mozilla::a11y::Accessible>,
     22                              MOXTextMarkerDelegate*>
     23    sDelegates;
     24 
     25 @implementation MOXTextMarkerDelegate
     26 
     27 + (id)getOrCreateForDoc:(mozilla::a11y::Accessible*)aDoc {
     28  MOZ_ASSERT(aDoc);
     29 
     30  MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc);
     31  if (!delegate) {
     32    delegate = [[MOXTextMarkerDelegate alloc] initWithDoc:aDoc];
     33    sDelegates.InsertOrUpdate(aDoc, delegate);
     34    [delegate retain];
     35  }
     36 
     37  return delegate;
     38 }
     39 
     40 + (void)destroyForDoc:(mozilla::a11y::Accessible*)aDoc {
     41  MOZ_ASSERT(aDoc);
     42 
     43  MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc);
     44  if (delegate) {
     45    sDelegates.Remove(aDoc);
     46    [delegate release];
     47  }
     48 }
     49 
     50 - (id)initWithDoc:(Accessible*)aDoc {
     51  MOZ_ASSERT(aDoc, "Cannot init MOXTextDelegate with null");
     52  if ((self = [super init])) {
     53    mGeckoDocAccessible = aDoc;
     54  }
     55 
     56  mCaretMoveGranularity = nsISelectionListener::NO_AMOUNT;
     57 
     58  return self;
     59 }
     60 
     61 - (void)dealloc {
     62  [self invalidateSelection];
     63  [super dealloc];
     64 }
     65 
     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));
     72 
     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();
     78  CFRetain(mSelection);
     79 }
     80 
     81 - (void)setCaretOffset:(mozilla::a11y::Accessible*)container
     82                    at:(int32_t)offset
     83       moveGranularity:(int32_t)granularity {
     84  GeckoTextMarker caretMarker(container, offset);
     85 
     86  mPrevCaret = mCaret;
     87  mCaret = caretMarker.CreateAXTextMarker();
     88  mCaretMoveGranularity = granularity;
     89 
     90  CFRetain(mCaret);
     91 }
     92 
     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];
    101    }
    102  }
    103 
    104  return nil;
    105 }
    106 
    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);
    114 
    115  int32_t stateChangeType =
    116      selectedGeckoRange.Start() == selectedGeckoRange.End()
    117          ? AXTextStateChangeTypeSelectionMove
    118          : AXTextStateChangeTypeSelectionExtend;
    119 
    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
    125        : [NSNull null],
    126    @"AXTextStateChangeType" : @(stateChangeType),
    127  } mutableCopy] autorelease];
    128 
    129  GeckoTextMarker caretMarker =
    130      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, mCaret);
    131  GeckoTextMarker prevCaretMarker =
    132      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, mPrevCaret);
    133 
    134  if (!caretMarker.IsValid()) {
    135    // If the current caret is invalid, stop here and return base info.
    136    return info;
    137  }
    138 
    139  mozAccessible* caretEditable =
    140      GetEditableNativeFromGeckoAccessible(caretMarker.Acc());
    141 
    142  if (!caretEditable && stateChangeType == AXTextStateChangeTypeSelectionMove) {
    143    // If we are not in an editable, VO expects AXTextStateSync to be present
    144    // and true.
    145    info[@"AXTextStateSync"] = @YES;
    146  }
    147 
    148  if (!prevCaretMarker.IsValid() || caretMarker == prevCaretMarker) {
    149    // If we have no stored previous marker, stop here.
    150    return info;
    151  }
    152 
    153  mozAccessible* prevCaretEditable =
    154      GetEditableNativeFromGeckoAccessible(prevCaretMarker.Acc());
    155 
    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;
    165    }
    166 
    167    return info;
    168  }
    169 
    170  bool isForward = prevCaretMarker < caretMarker;
    171  int direction = isForward ? AXTextSelectionDirectionNext
    172                            : AXTextSelectionDirectionPrevious;
    173 
    174  int32_t granularity = AXTextSelectionGranularityUnknown;
    175  switch (mCaretMoveGranularity) {
    176    case nsISelectionListener::CHARACTER_AMOUNT:
    177    case nsISelectionListener::CLUSTER_AMOUNT:
    178      granularity = AXTextSelectionGranularityCharacter;
    179      break;
    180    case nsISelectionListener::WORD_AMOUNT:
    181    case nsISelectionListener::WORDNOSPACE_AMOUNT:
    182      granularity = AXTextSelectionGranularityWord;
    183      break;
    184    case nsISelectionListener::LINE_AMOUNT:
    185      granularity = AXTextSelectionGranularityLine;
    186      break;
    187    case nsISelectionListener::BEGINLINE_AMOUNT:
    188      direction = AXTextSelectionDirectionBeginning;
    189      granularity = AXTextSelectionGranularityLine;
    190      break;
    191    case nsISelectionListener::ENDLINE_AMOUNT:
    192      direction = AXTextSelectionDirectionEnd;
    193      granularity = AXTextSelectionGranularityLine;
    194      break;
    195    case nsISelectionListener::PARAGRAPH_AMOUNT:
    196      granularity = AXTextSelectionGranularityParagraph;
    197      break;
    198    default:
    199      break;
    200  }
    201 
    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)
    208  }];
    209 
    210  return info;
    211 }
    212 
    213 - (void)invalidateSelection {
    214  CFRelease(mSelection);
    215  CFRelease(mCaret);
    216  CFRelease(mPrevCaret);
    217  mSelection = nil;
    218 }
    219 
    220 - (mozilla::a11y::GeckoTextMarkerRange)selection {
    221  return mozilla::a11y::GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    222      mGeckoDocAccessible, mSelection);
    223 }
    224 
    225 - (AXTextMarkerRef)moxStartTextMarker {
    226  GeckoTextMarker geckoTextPoint(mGeckoDocAccessible, 0);
    227  return geckoTextPoint.CreateAXTextMarker();
    228 }
    229 
    230 - (AXTextMarkerRef)moxEndTextMarker {
    231  GeckoTextMarker geckoTextPoint(mGeckoDocAccessible,
    232                                 nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
    233  return geckoTextPoint.CreateAXTextMarker();
    234 }
    235 
    236 - (AXTextMarkerRangeRef)moxSelectedTextMarkerRange {
    237  return mSelection && GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    238                           mGeckoDocAccessible, mSelection)
    239                           .IsValid()
    240             ? mSelection
    241             : nil;
    242 }
    243 
    244 - (NSString*)moxStringForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
    245  mozilla::a11y::GeckoTextMarkerRange range =
    246      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    247          mGeckoDocAccessible, textMarkerRange);
    248  if (!range.IsValid()) {
    249    return @"";
    250  }
    251 
    252  return range.Text();
    253 }
    254 
    255 - (NSNumber*)moxLengthForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
    256  mozilla::a11y::GeckoTextMarkerRange range =
    257      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    258          mGeckoDocAccessible, textMarkerRange);
    259  if (!range.IsValid()) {
    260    return @0;
    261  }
    262 
    263  return @(range.Length());
    264 }
    265 
    266 - (AXTextMarkerRangeRef)moxTextMarkerRangeForUnorderedTextMarkers:
    267    (NSArray*)textMarkers {
    268  if ([textMarkers count] != 2) {
    269    // Don't allow anything but a two member array.
    270    return nil;
    271  }
    272 
    273  GeckoTextMarker p1 = GeckoTextMarker::MarkerFromAXTextMarker(
    274      mGeckoDocAccessible, (__bridge AXTextMarkerRef)textMarkers[0]);
    275  GeckoTextMarker p2 = GeckoTextMarker::MarkerFromAXTextMarker(
    276      mGeckoDocAccessible, (__bridge AXTextMarkerRef)textMarkers[1]);
    277 
    278  if (!p1.IsValid() || !p2.IsValid()) {
    279    // If either marker is invalid, return nil.
    280    return nil;
    281  }
    282 
    283  bool ordered = p1 < p2;
    284  GeckoTextMarkerRange range(ordered ? p1 : p2, ordered ? p2 : p1);
    285 
    286  return range.CreateAXTextMarkerRange();
    287 }
    288 
    289 - (AXTextMarkerRef)moxStartTextMarkerForTextMarkerRange:
    290    (AXTextMarkerRangeRef)textMarkerRange {
    291  mozilla::a11y::GeckoTextMarkerRange range =
    292      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    293          mGeckoDocAccessible, textMarkerRange);
    294 
    295  return range.IsValid() ? range.Start().CreateAXTextMarker() : nil;
    296 }
    297 
    298 - (AXTextMarkerRef)moxEndTextMarkerForTextMarkerRange:
    299    (AXTextMarkerRangeRef)textMarkerRange {
    300  mozilla::a11y::GeckoTextMarkerRange range =
    301      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    302          mGeckoDocAccessible, textMarkerRange);
    303 
    304  return range.IsValid() ? range.End().CreateAXTextMarker() : nil;
    305 }
    306 
    307 - (AXTextMarkerRangeRef)moxLeftWordTextMarkerRangeForTextMarker:
    308    (AXTextMarkerRef)textMarker {
    309  GeckoTextMarker geckoTextMarker =
    310      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    311  if (!geckoTextMarker.IsValid()) {
    312    return nil;
    313  }
    314 
    315  return geckoTextMarker.LeftWordRange().CreateAXTextMarkerRange();
    316 }
    317 
    318 - (AXTextMarkerRangeRef)moxRightWordTextMarkerRangeForTextMarker:
    319    (AXTextMarkerRef)textMarker {
    320  GeckoTextMarker geckoTextMarker =
    321      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    322  if (!geckoTextMarker.IsValid()) {
    323    return nil;
    324  }
    325 
    326  return geckoTextMarker.RightWordRange().CreateAXTextMarkerRange();
    327 }
    328 
    329 - (AXTextMarkerRangeRef)moxLineTextMarkerRangeForTextMarker:
    330    (AXTextMarkerRef)textMarker {
    331  GeckoTextMarker geckoTextMarker =
    332      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    333  if (!geckoTextMarker.IsValid()) {
    334    return nil;
    335  }
    336 
    337  return geckoTextMarker.LineRange().CreateAXTextMarkerRange();
    338 }
    339 
    340 - (AXTextMarkerRangeRef)moxLeftLineTextMarkerRangeForTextMarker:
    341    (AXTextMarkerRef)textMarker {
    342  GeckoTextMarker geckoTextMarker =
    343      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    344  if (!geckoTextMarker.IsValid()) {
    345    return nil;
    346  }
    347 
    348  return geckoTextMarker.LeftLineRange().CreateAXTextMarkerRange();
    349 }
    350 
    351 - (AXTextMarkerRangeRef)moxRightLineTextMarkerRangeForTextMarker:
    352    (AXTextMarkerRef)textMarker {
    353  GeckoTextMarker geckoTextMarker =
    354      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    355  if (!geckoTextMarker.IsValid()) {
    356    return nil;
    357  }
    358 
    359  return geckoTextMarker.RightLineRange().CreateAXTextMarkerRange();
    360 }
    361 
    362 - (AXTextMarkerRangeRef)moxParagraphTextMarkerRangeForTextMarker:
    363    (AXTextMarkerRef)textMarker {
    364  GeckoTextMarker geckoTextMarker =
    365      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    366  if (!geckoTextMarker.IsValid()) {
    367    return nil;
    368  }
    369 
    370  return geckoTextMarker.ParagraphRange().CreateAXTextMarkerRange();
    371 }
    372 
    373 // override
    374 - (AXTextMarkerRangeRef)moxStyleTextMarkerRangeForTextMarker:
    375    (AXTextMarkerRef)textMarker {
    376  GeckoTextMarker geckoTextMarker =
    377      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    378  if (!geckoTextMarker.IsValid()) {
    379    return nil;
    380  }
    381 
    382  return geckoTextMarker.StyleRange().CreateAXTextMarkerRange();
    383 }
    384 
    385 - (AXTextMarkerRef)moxNextTextMarkerForTextMarker:(AXTextMarkerRef)textMarker {
    386  GeckoTextMarker geckoTextMarker =
    387      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    388  if (!geckoTextMarker.IsValid()) {
    389    return nil;
    390  }
    391 
    392  if (!geckoTextMarker.Next()) {
    393    return nil;
    394  }
    395 
    396  return geckoTextMarker.CreateAXTextMarker();
    397 }
    398 
    399 - (AXTextMarkerRef)moxPreviousTextMarkerForTextMarker:
    400    (AXTextMarkerRef)textMarker {
    401  GeckoTextMarker geckoTextMarker =
    402      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    403  if (!geckoTextMarker.IsValid()) {
    404    return nil;
    405  }
    406 
    407  if (!geckoTextMarker.Previous()) {
    408    return nil;
    409  }
    410 
    411  return geckoTextMarker.CreateAXTextMarker();
    412 }
    413 
    414 - (NSAttributedString*)moxAttributedStringForTextMarkerRange:
    415    (AXTextMarkerRangeRef)textMarkerRange {
    416  mozilla::a11y::GeckoTextMarkerRange range =
    417      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    418          mGeckoDocAccessible, textMarkerRange);
    419  if (!range.IsValid()) {
    420    return nil;
    421  }
    422 
    423  return range.AttributedText();
    424 }
    425 
    426 - (NSValue*)moxBoundsForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
    427  mozilla::a11y::GeckoTextMarkerRange range =
    428      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    429          mGeckoDocAccessible, textMarkerRange);
    430  if (!range.IsValid()) {
    431    return nil;
    432  }
    433 
    434  return range.Bounds();
    435 }
    436 
    437 - (NSNumber*)moxIndexForTextMarker:(AXTextMarkerRef)textMarker {
    438  GeckoTextMarker geckoTextMarker =
    439      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    440  if (!geckoTextMarker.IsValid()) {
    441    return nil;
    442  }
    443 
    444  GeckoTextMarkerRange range(GeckoTextMarker(mGeckoDocAccessible, 0),
    445                             geckoTextMarker);
    446 
    447  return @(range.Length());
    448 }
    449 
    450 - (AXTextMarkerRef)moxTextMarkerForIndex:(NSNumber*)index {
    451  GeckoTextMarker geckoTextMarker = GeckoTextMarker::MarkerFromIndex(
    452      mGeckoDocAccessible, [index integerValue]);
    453  if (!geckoTextMarker.IsValid()) {
    454    return nil;
    455  }
    456 
    457  return geckoTextMarker.CreateAXTextMarker();
    458 }
    459 
    460 - (id)moxUIElementForTextMarker:(AXTextMarkerRef)textMarker {
    461  GeckoTextMarker geckoTextMarker =
    462      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    463  if (!geckoTextMarker.IsValid()) {
    464    return nil;
    465  }
    466 
    467  Accessible* leaf = geckoTextMarker.Leaf();
    468  if (!leaf) {
    469    return nil;
    470  }
    471 
    472  return GetNativeFromGeckoAccessible(leaf);
    473 }
    474 
    475 - (AXTextMarkerRangeRef)moxTextMarkerRangeForUIElement:(id)element {
    476  if (![element isKindOfClass:[mozAccessible class]]) {
    477    return nil;
    478  }
    479 
    480  GeckoTextMarkerRange range((Accessible*)[element geckoAccessible]);
    481  return range.CreateAXTextMarkerRange();
    482 }
    483 
    484 - (NSString*)moxMozDebugDescriptionForTextMarker:(AXTextMarkerRef)textMarker {
    485  if (!mozilla::Preferences::GetBool(PREF_ACCESSIBILITY_MAC_DEBUG)) {
    486    return nil;
    487  }
    488 
    489  GeckoTextMarker geckoTextMarker =
    490      GeckoTextMarker::MarkerFromAXTextMarker(mGeckoDocAccessible, textMarker);
    491  if (!geckoTextMarker.IsValid()) {
    492    return @"<GeckoTextMarker 0x0 [0]>";
    493  }
    494 
    495  return [NSString stringWithFormat:@"<GeckoTextMarker %p [%d]>",
    496                                    geckoTextMarker.Acc(),
    497                                    geckoTextMarker.Offset()];
    498 }
    499 
    500 - (NSString*)moxMozDebugDescriptionForTextMarkerRange:
    501    (AXTextMarkerRangeRef)textMarkerRange {
    502  if (!mozilla::Preferences::GetBool(PREF_ACCESSIBILITY_MAC_DEBUG)) {
    503    return nil;
    504  }
    505 
    506  mozilla::a11y::GeckoTextMarkerRange range =
    507      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    508          mGeckoDocAccessible, textMarkerRange);
    509  if (!range.IsValid()) {
    510    return @"<GeckoTextMarkerRange 0x0 [0] - 0x0 [0]>";
    511  }
    512 
    513  return [NSString stringWithFormat:@"<GeckoTextMarkerRange %p [%d] - %p [%d]>",
    514                                    range.Start().Acc(), range.Start().Offset(),
    515                                    range.End().Acc(), range.End().Offset()];
    516 }
    517 
    518 - (void)moxSetSelectedTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange {
    519  mozilla::a11y::GeckoTextMarkerRange range =
    520      GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    521          mGeckoDocAccessible, textMarkerRange);
    522  if (range.IsValid()) {
    523    range.Select();
    524  }
    525 }
    526 
    527 @end