tor-browser

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

MOXAccessibleBase.mm (17204B)


      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 <Accessibility/Accessibility.h>
      9 
     10 #import "MOXAccessibleBase.h"
     11 
     12 #import "MacSelectorMap.h"
     13 
     14 #include "nsObjCExceptions.h"
     15 #include "xpcAccessibleMacInterface.h"
     16 #include "mozilla/Logging.h"
     17 #include "gfxPlatform.h"
     18 
     19 using namespace mozilla;
     20 using namespace mozilla::a11y;
     21 
     22 #undef LOG
     23 mozilla::LogModule* GetMacAccessibilityLog() {
     24  static mozilla::LazyLogModule sLog("MacAccessibility");
     25 
     26  return sLog;
     27 }
     28 #define LOG(type, format, ...)                                             \
     29  do {                                                                     \
     30    if (MOZ_LOG_TEST(GetMacAccessibilityLog(), type)) {                    \
     31      NSString* msg = [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
     32      MOZ_LOG(GetMacAccessibilityLog(), type, ("%s", [msg UTF8String]));   \
     33    }                                                                      \
     34  } while (0)
     35 
     36 @interface NSObject (MOXAccessible)
     37 
     38 // This NSObject conforms to MOXAccessible.
     39 // This is needed to we know to mutate the value
     40 // (get represented view, check isAccessibilityElement)
     41 // before forwarding it to NSAccessibility.
     42 - (BOOL)isMOXAccessible;
     43 
     44 // Same as above, but this checks if the NSObject is an array with
     45 // mozAccessible conforming objects.
     46 - (BOOL)hasMOXAccessibles;
     47 
     48 @end
     49 
     50 @implementation NSObject (MOXAccessible)
     51 
     52 - (BOOL)isMOXAccessible {
     53  return [self conformsToProtocol:@protocol(MOXAccessible)];
     54 }
     55 
     56 - (BOOL)hasMOXAccessibles {
     57  return [self isKindOfClass:[NSArray class]] &&
     58         [[(NSArray*)self firstObject] isMOXAccessible];
     59 }
     60 
     61 @end
     62 
     63 // Private methods
     64 @interface MOXAccessibleBase ()
     65 
     66 - (BOOL)isSelectorSupported:(SEL)selector;
     67 
     68 @end
     69 
     70 @implementation MOXAccessibleBase
     71 
     72 #pragma mark - mozAccessible/widget
     73 
     74 - (BOOL)hasRepresentedView {
     75  return NO;
     76 }
     77 
     78 - (id)representedView {
     79  return nil;
     80 }
     81 
     82 - (BOOL)isRoot {
     83  return NO;
     84 }
     85 
     86 #pragma mark - mozAccessible/NSAccessibility
     87 
     88 - (NSArray*)accessibilityAttributeNames {
     89  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
     90 
     91  if ([self isExpired]) {
     92    return nil;
     93  }
     94 
     95  static NSMutableDictionary* attributesForEachClass = nil;
     96 
     97  if (!attributesForEachClass) {
     98    attributesForEachClass = [[NSMutableDictionary alloc] init];
     99  }
    100 
    101  NSMutableArray* attributes =
    102      attributesForEachClass [[self class]]
    103          ?: [[[NSMutableArray alloc] init] autorelease];
    104 
    105  NSDictionary* getters = mac::AttributeGetters();
    106  if (![attributes count]) {
    107    // Go through all our attribute getters, if they are supported by this class
    108    // advertise the attribute name.
    109    for (NSString* attribute in getters) {
    110      SEL selector = NSSelectorFromString(getters[attribute]);
    111      if ([self isSelectorSupported:selector]) {
    112        [attributes addObject:attribute];
    113      }
    114    }
    115 
    116    // If we have a delegate add all the text marker attributes.
    117    if ([self moxTextMarkerDelegate]) {
    118      [attributes addObjectsFromArray:[mac::TextAttributeGetters() allKeys]];
    119    }
    120 
    121    // We store a hash table with types as keys, and atttribute lists as values.
    122    // This lets us cache the atttribute list of each subclass so we only
    123    // need to gather its MOXAccessible methods once.
    124    // XXX: Uncomment when accessibilityAttributeNames is removed from all
    125    // subclasses. attributesForEachClass[[self class]] = attributes;
    126  }
    127 
    128  return attributes;
    129 
    130  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    131 }
    132 
    133 - (id)accessibilityAttributeValue:(NSString*)attribute {
    134  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    135  if ([self isExpired]) {
    136    return nil;
    137  }
    138 
    139  id value = nil;
    140  NSDictionary* getters = mac::AttributeGetters();
    141  if (getters[attribute]) {
    142    SEL selector = NSSelectorFromString(getters[attribute]);
    143    if ([self isSelectorSupported:selector]) {
    144      value = [self performSelector:selector];
    145    }
    146  } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
    147    // If we have a delegate, check if attribute is a text marker
    148    // attribute and call the associated selector on the delegate
    149    // if so.
    150    NSDictionary* textMarkerGetters = mac::TextAttributeGetters();
    151    if (textMarkerGetters[attribute]) {
    152      SEL selector = NSSelectorFromString(textMarkerGetters[attribute]);
    153      if ([textMarkerDelegate respondsToSelector:selector]) {
    154        value = [textMarkerDelegate performSelector:selector];
    155      }
    156    }
    157  }
    158 
    159  if ([value isMOXAccessible]) {
    160    // If this is a MOXAccessible, get its represented view or filter it if
    161    // it should be ignored.
    162    value = [value isAccessibilityElement] ? GetObjectOrRepresentedView(value)
    163                                           : nil;
    164  }
    165 
    166  if ([value hasMOXAccessibles]) {
    167    // If this is an array of mozAccessibles, get each element's represented
    168    // view and remove it from the returned array if it should be ignored.
    169    NSUInteger arrSize = [value count];
    170    NSMutableArray* arr =
    171        [[[NSMutableArray alloc] initWithCapacity:arrSize] autorelease];
    172    for (NSUInteger i = 0; i < arrSize; i++) {
    173      id<mozAccessible> mozAcc = GetObjectOrRepresentedView(value[i]);
    174      if ([mozAcc isAccessibilityElement]) {
    175        [arr addObject:mozAcc];
    176      }
    177    }
    178 
    179    value = arr;
    180  }
    181 
    182  if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Debug)) {
    183    if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Verbose)) {
    184      LOG(LogLevel::Verbose, @"%@ attributeValue %@ => %@", self, attribute,
    185          value);
    186    } else if (![attribute isEqualToString:@"AXParent"] &&
    187               ![attribute isEqualToString:@"AXRole"] &&
    188               ![attribute isEqualToString:@"AXSubrole"] &&
    189               ![attribute isEqualToString:@"AXSize"] &&
    190               ![attribute isEqualToString:@"AXPosition"] &&
    191               ![attribute isEqualToString:@"AXRole"] &&
    192               ![attribute isEqualToString:@"AXChildren"]) {
    193      LOG(LogLevel::Debug, @"%@ attributeValue %@", self, attribute);
    194    }
    195  }
    196 
    197  return value;
    198 
    199  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    200 }
    201 
    202 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
    203  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    204 
    205  if ([self isExpired]) {
    206    return NO;
    207  }
    208 
    209  NSDictionary* setters = mac::AttributeSetters();
    210  if (setters[attribute]) {
    211    SEL selector = NSSelectorFromString(setters[attribute]);
    212    if ([self isSelectorSupported:selector]) {
    213      return YES;
    214    }
    215  } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
    216    // If we have a delegate, check text setters on delegate
    217    NSDictionary* textMarkerSetters = mac::TextAttributeSetters();
    218    if (textMarkerSetters[attribute]) {
    219      SEL selector = NSSelectorFromString(textMarkerSetters[attribute]);
    220      if ([textMarkerDelegate respondsToSelector:selector]) {
    221        return YES;
    222      }
    223    }
    224  }
    225 
    226  NS_OBJC_END_TRY_BLOCK_RETURN(NO);
    227 }
    228 
    229 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
    230  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
    231 
    232  if ([self isExpired]) {
    233    return;
    234  }
    235 
    236  LOG(LogLevel::Debug, @"%@ setValueForattribute %@ = %@", self, attribute,
    237      value);
    238 
    239  NSDictionary* setters = mac::AttributeSetters();
    240  if (setters[attribute]) {
    241    SEL selector = NSSelectorFromString(setters[attribute]);
    242    if ([self isSelectorSupported:selector]) {
    243      [self performSelector:selector withObject:value];
    244    }
    245  } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
    246    // If we have a delegate, check if attribute is a text marker
    247    // attribute and call the associated selector on the delegate
    248    // if so.
    249    NSDictionary* textMarkerSetters = mac::TextAttributeSetters();
    250    if (textMarkerSetters[attribute]) {
    251      SEL selector = NSSelectorFromString(textMarkerSetters[attribute]);
    252      if ([textMarkerDelegate respondsToSelector:selector]) {
    253        [textMarkerDelegate performSelector:selector withObject:value];
    254      }
    255    }
    256  }
    257 
    258  NS_OBJC_END_TRY_IGNORE_BLOCK;
    259 }
    260 
    261 - (NSArray*)accessibilityActionNames {
    262  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    263 
    264  if ([self isExpired]) {
    265    return nil;
    266  }
    267 
    268  NSMutableArray* actionNames = [[[NSMutableArray alloc] init] autorelease];
    269 
    270  NSDictionary* actions = mac::Actions();
    271  for (NSString* action in actions) {
    272    SEL selector = NSSelectorFromString(actions[action]);
    273    if ([self isSelectorSupported:selector]) {
    274      [actionNames addObject:action];
    275    }
    276  }
    277 
    278  return actionNames;
    279 
    280  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    281 }
    282 
    283 - (void)accessibilityPerformAction:(NSString*)action {
    284  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
    285 
    286  if ([self isExpired]) {
    287    return;
    288  }
    289 
    290  LOG(LogLevel::Debug, @"%@ performAction %@ ", self, action);
    291 
    292  NSDictionary* actions = mac::Actions();
    293  if (actions[action]) {
    294    SEL selector = NSSelectorFromString(actions[action]);
    295    if ([self isSelectorSupported:selector]) {
    296      [self performSelector:selector];
    297    }
    298  }
    299 
    300  NS_OBJC_END_TRY_IGNORE_BLOCK;
    301 }
    302 
    303 - (NSString*)accessibilityActionDescription:(NSString*)action {
    304  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    305  // by default we return whatever the MacOS API know about.
    306  // if you have custom actions, override.
    307  return NSAccessibilityActionDescription(action);
    308  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    309 }
    310 
    311 - (NSArray*)accessibilityParameterizedAttributeNames {
    312  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    313 
    314  if ([self isExpired]) {
    315    return nil;
    316  }
    317 
    318  NSMutableArray* attributeNames = [[[NSMutableArray alloc] init] autorelease];
    319 
    320  NSDictionary* attributes = mac::ParameterizedAttributeGetters();
    321  for (NSString* attribute in attributes) {
    322    SEL selector = NSSelectorFromString(attributes[attribute]);
    323    if ([self isSelectorSupported:selector]) {
    324      [attributeNames addObject:attribute];
    325    }
    326  }
    327 
    328  // If we have a delegate add all the text marker attributes.
    329  if ([self moxTextMarkerDelegate]) {
    330    [attributeNames
    331        addObjectsFromArray:[mac::ParameterizedTextAttributeGetters() allKeys]];
    332  }
    333 
    334  return attributeNames;
    335 
    336  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    337 }
    338 
    339 - (id)accessibilityAttributeValue:(NSString*)attribute
    340                     forParameter:(id)parameter {
    341  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    342 
    343  if ([self isExpired]) {
    344    return nil;
    345  }
    346 
    347  id value = nil;
    348 
    349  NSDictionary* getters = mac::ParameterizedAttributeGetters();
    350  if (getters[attribute]) {
    351    SEL selector = NSSelectorFromString(getters[attribute]);
    352    if ([self isSelectorSupported:selector]) {
    353      value = [self performSelector:selector withObject:parameter];
    354    }
    355  } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
    356    // If we have a delegate, check if attribute is a text marker
    357    // attribute and call the associated selector on the delegate
    358    // if so.
    359    NSDictionary* textMarkerGetters = mac::ParameterizedTextAttributeGetters();
    360    if (textMarkerGetters[attribute]) {
    361      SEL selector = NSSelectorFromString(textMarkerGetters[attribute]);
    362      if ([textMarkerDelegate respondsToSelector:selector]) {
    363        value = [textMarkerDelegate performSelector:selector
    364                                         withObject:parameter];
    365      }
    366    }
    367  }
    368 
    369  if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Verbose)) {
    370    LOG(LogLevel::Verbose, @"%@ attributeValueForParam %@(%@) => %@", self,
    371        attribute, parameter, value);
    372  } else {
    373    LOG(LogLevel::Debug, @"%@ attributeValueForParam %@", self, attribute);
    374  }
    375 
    376  return value;
    377 
    378  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    379 }
    380 
    381 - (id)accessibilityHitTest:(NSPoint)point {
    382  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    383  return GetObjectOrRepresentedView([self moxHitTest:point]);
    384  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    385 }
    386 
    387 - (id)accessibilityFocusedUIElement {
    388  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    389  return GetObjectOrRepresentedView([self moxFocusedUIElement]);
    390  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    391 }
    392 
    393 - (id)accessibilityCustomActions {
    394  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    395  return [self moxCustomActions];
    396  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
    397 }
    398 
    399 - (BOOL)isAccessibilityElement {
    400  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
    401 
    402  if ([self isExpired]) {
    403    return YES;
    404  }
    405 
    406  id parent = [self moxParent];
    407  if (![parent isMOXAccessible]) {
    408    return YES;
    409  }
    410 
    411  return ![self moxIgnoreWithParent:parent];
    412 
    413  NS_OBJC_END_TRY_BLOCK_RETURN(NO);
    414 }
    415 
    416 - (BOOL)accessibilityNotifiesWhenDestroyed {
    417  return YES;
    418 }
    419 
    420 #pragma mark - AXCustomContentProvider protocol
    421 
    422 - (NSArray*)accessibilityCustomContent {
    423  return [self moxCustomContent];
    424 }
    425 
    426 #pragma mark - MOXAccessible protocol
    427 
    428 - (NSNumber*)moxIndexForChildUIElement:(id)child {
    429  return @([[self moxUnignoredChildren] indexOfObject:child]);
    430 }
    431 
    432 - (id)moxTopLevelUIElement {
    433  return [self moxWindow];
    434 }
    435 
    436 - (id)moxHitTest:(NSPoint)point {
    437  return self;
    438 }
    439 
    440 - (id)moxFocusedUIElement {
    441  return self;
    442 }
    443 
    444 - (void)moxPostNotification:(NSString*)notification {
    445  [self moxPostNotification:notification withUserInfo:nil];
    446 }
    447 
    448 - (void)moxPostNotification:(NSString*)notification
    449               withUserInfo:(NSDictionary*)userInfo {
    450  if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Verbose)) {
    451    LOG(LogLevel::Verbose, @"%@ notify %@ %@", self, notification, userInfo);
    452  } else {
    453    LOG(LogLevel::Debug, @"%@ notify %@", self, notification);
    454  }
    455 
    456  // This sends events via nsIObserverService to be consumed by our mochitests.
    457  xpcAccessibleMacEvent::FireEvent(self, notification, userInfo);
    458 
    459  if (gfxPlatform::IsHeadless()) {
    460    // Using a headless toolkit for tests and whatnot, posting accessibility
    461    // notification won't work.
    462    return;
    463  }
    464 
    465  if (![self isAccessibilityElement]) {
    466    // If this is an ignored object, don't expose it to system.
    467    return;
    468  }
    469 
    470  if (userInfo) {
    471    NSAccessibilityPostNotificationWithUserInfo(
    472        GetObjectOrRepresentedView(self), notification, userInfo);
    473  } else {
    474    NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
    475                                    notification);
    476  }
    477 }
    478 
    479 - (BOOL)moxBlockSelector:(SEL)selector {
    480  return NO;
    481 }
    482 
    483 - (NSArray*)moxChildren {
    484  return @[];
    485 }
    486 
    487 - (NSArray*)moxUnignoredChildren {
    488  NSMutableArray* unignoredChildren =
    489      [[[NSMutableArray alloc] init] autorelease];
    490  NSArray* allChildren = [self moxChildren];
    491 
    492  for (MOXAccessibleBase* nativeChild in allChildren) {
    493    if ([nativeChild moxIgnoreWithParent:self]) {
    494      // If this child should be ignored get its unignored children.
    495      // This will in turn recurse to any unignored descendants if the
    496      // child is ignored.
    497      [unignoredChildren
    498          addObjectsFromArray:[nativeChild moxUnignoredChildren]];
    499    } else {
    500      [unignoredChildren addObject:nativeChild];
    501    }
    502  }
    503 
    504  return unignoredChildren;
    505 }
    506 
    507 - (id<mozAccessible>)moxParent {
    508  return nil;
    509 }
    510 
    511 - (id<mozAccessible>)moxUnignoredParent {
    512  id<mozAccessible> nativeParent = [self moxParent];
    513  if (!nativeParent) {
    514    return nil;
    515  }
    516 
    517  if (![nativeParent isAccessibilityElement]) {
    518    if ([nativeParent conformsToProtocol:@protocol(MOXAccessible)] &&
    519        [nativeParent respondsToSelector:@selector(moxUnignoredParent)]) {
    520      // Cast away the protocol so we can cast to another protocol.
    521      id bareNativeParent = nativeParent;
    522      id<MOXAccessible> moxNativeParent = bareNativeParent;
    523      return [moxNativeParent moxUnignoredParent];
    524    }
    525  }
    526 
    527  return GetObjectOrRepresentedView(nativeParent);
    528 }
    529 
    530 - (BOOL)moxIgnoreWithParent:(MOXAccessibleBase*)parent {
    531  return [parent moxIgnoreChild:self];
    532 }
    533 
    534 - (BOOL)moxIgnoreChild:(MOXAccessibleBase*)child {
    535  return NO;
    536 }
    537 
    538 - (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
    539  return nil;
    540 }
    541 
    542 - (BOOL)moxIsLiveRegion {
    543  return NO;
    544 }
    545 
    546 - (BOOL)moxIsTextField {
    547  return NO;
    548 }
    549 
    550 #pragma mark -
    551 
    552 // objc-style description (from NSObject); not to be confused with the
    553 // accessible description above.
    554 - (NSString*)description {
    555  if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Debug)) {
    556    if ([self isSelectorSupported:@selector(moxMozDebugDescription)]) {
    557      return [self moxMozDebugDescription];
    558    }
    559  }
    560 
    561  return [NSString stringWithFormat:@"<%@: %p %@>",
    562                                    NSStringFromClass([self class]), self,
    563                                    [self moxRole]];
    564 }
    565 
    566 - (BOOL)isExpired {
    567  return mIsExpired;
    568 }
    569 
    570 - (void)expire {
    571  MOZ_ASSERT(!mIsExpired, "expire called an expired mozAccessible!");
    572 
    573  mIsExpired = YES;
    574 
    575  [self moxPostNotification:NSAccessibilityUIElementDestroyedNotification];
    576 }
    577 
    578 - (id<MOXAccessible>)moxFindAncestor:(BOOL (^)(id<MOXAccessible> moxAcc,
    579                                               BOOL* stop))findBlock {
    580  for (id element = self; [element conformsToProtocol:@protocol(MOXAccessible)];
    581       element = [element moxUnignoredParent]) {
    582    BOOL stop = NO;
    583    if (findBlock(element, &stop)) {
    584      return element;
    585    }
    586 
    587    if (stop || ![element respondsToSelector:@selector(moxUnignoredParent)]) {
    588      break;
    589    }
    590  }
    591 
    592  return nil;
    593 }
    594 
    595 - (NSArray*)moxCustomContent {
    596  return nil;
    597 }
    598 
    599 - (NSArray*)moxCustomActions {
    600  return nil;
    601 }
    602 
    603 #pragma mark - Private
    604 
    605 - (BOOL)isSelectorSupported:(SEL)selector {
    606  return
    607      [self respondsToSelector:selector] && ![self moxBlockSelector:selector];
    608 }
    609 
    610 @end