tor-browser

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

MOXWebAreaAccessible.mm (7145B)


      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 "MOXWebAreaAccessible.h"
      9 
     10 #import "MOXSearchInfo.h"
     11 #import "MacUtils.h"
     12 
     13 #include "nsAccUtils.h"
     14 #include "nsCocoaUtils.h"
     15 #include "DocAccessible.h"
     16 #include "DocAccessibleParent.h"
     17 
     18 using namespace mozilla::a11y;
     19 
     20 @implementation MOXRootGroup
     21 
     22 - (id)initWithParent:(MOXWebAreaAccessible*)parent {
     23  // The parent is always a MOXWebAreaAccessible
     24  mParent = parent;
     25  return [super init];
     26 }
     27 
     28 - (NSString*)moxRole {
     29  return NSAccessibilityGroupRole;
     30 }
     31 
     32 - (NSString*)moxRoleDescription {
     33  if ([[self moxSubrole] isEqualToString:@"AXLandmarkApplication"]) {
     34    return utils::LocalizedString(u"application"_ns);
     35  }
     36 
     37  return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
     38 }
     39 
     40 - (id<mozAccessible>)moxParent {
     41  return mParent;
     42 }
     43 
     44 - (NSArray*)moxChildren {
     45  // Reparent the children of the web area here.
     46  return [mParent rootGroupChildren];
     47 }
     48 
     49 - (NSString*)moxIdentifier {
     50  // This is mostly for testing purposes to assert that this is the generated
     51  // root group.
     52  return @"root-group";
     53 }
     54 
     55 - (NSString*)moxSubrole {
     56  // Steal the subrole internally mapped to the web area.
     57  return [mParent moxSubrole];
     58 }
     59 
     60 - (id)moxHitTest:(NSPoint)point {
     61  return [mParent moxHitTest:point];
     62 }
     63 
     64 - (NSValue*)moxPosition {
     65  return [mParent moxPosition];
     66 }
     67 
     68 - (NSValue*)moxSize {
     69  return [mParent moxSize];
     70 }
     71 
     72 - (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate {
     73  MOXSearchInfo* search =
     74      [[[MOXSearchInfo alloc] initWithParameters:searchPredicate
     75                                         andRoot:self] autorelease];
     76 
     77  return [search performSearch];
     78 }
     79 
     80 - (NSNumber*)moxUIElementCountForSearchPredicate:
     81    (NSDictionary*)searchPredicate {
     82  return [NSNumber
     83      numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
     84                           count]];
     85 }
     86 
     87 - (BOOL)disableChild:(id)child {
     88  return NO;
     89 }
     90 
     91 - (void)expire {
     92  mParent = nil;
     93  [super expire];
     94 }
     95 
     96 - (BOOL)isExpired {
     97  MOZ_ASSERT((mParent == nil) == mIsExpired);
     98 
     99  return [super isExpired];
    100 }
    101 
    102 @end
    103 
    104 @implementation MOXWebAreaAccessible
    105 
    106 - (NSString*)moxRole {
    107  // The OS role is AXWebArea regardless of the gecko role
    108  // (APPLICATION or DOCUMENT).
    109  // If the web area has a role of APPLICATION, its root group will
    110  // reflect that in a subrole/description.
    111  return @"AXWebArea";
    112 }
    113 
    114 - (NSString*)moxRoleDescription {
    115  // The role description is "HTML Content" regardless of the gecko role
    116  // (APPLICATION or DOCUMENT)
    117  return utils::LocalizedString(u"htmlContent"_ns);
    118 }
    119 
    120 - (NSURL*)moxURL {
    121  if ([self isExpired]) {
    122    return nil;
    123  }
    124 
    125  nsAutoString url;
    126  MOZ_ASSERT(mGeckoAccessible->IsDoc());
    127  nsAccUtils::DocumentURL(mGeckoAccessible, url);
    128 
    129  if (url.IsEmpty()) {
    130    return nil;
    131  }
    132 
    133  return [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
    134 }
    135 
    136 - (NSNumber*)moxLoaded {
    137  if ([self isExpired]) {
    138    return nil;
    139  }
    140  // We are loaded if we aren't busy or stale
    141  return @([self stateWithMask:(states::BUSY & states::STALE)] == 0);
    142 }
    143 
    144 // overrides
    145 - (NSNumber*)moxLoadingProgress {
    146  if ([self isExpired]) {
    147    return nil;
    148  }
    149 
    150  if ([self stateWithMask:states::STALE] != 0) {
    151    // We expose stale state until the document is ready (DOM is loaded and tree
    152    // is constructed) so we indicate load hasn't started while this state is
    153    // present.
    154    return @0.0;
    155  }
    156 
    157  if ([self stateWithMask:states::BUSY] != 0) {
    158    // We expose state busy until the document and all its subdocuments are
    159    // completely loaded, so we indicate partial loading here
    160    return @0.5;
    161  }
    162 
    163  // if we are not busy and not stale, we are loaded
    164  return @1.0;
    165 }
    166 
    167 - (NSArray*)moxLinkUIElements {
    168  NSDictionary* searchPredicate = @{
    169    @"AXSearchKey" : @"AXLinkSearchKey",
    170    @"AXImmediateDescendantsOnly" : @NO,
    171    @"AXResultsLimit" : @(-1),
    172    @"AXDirection" : @"AXDirectionNext",
    173  };
    174 
    175  return [self moxUIElementsForSearchPredicate:searchPredicate];
    176 }
    177 
    178 - (void)handleAccessibleEvent:(uint32_t)eventType {
    179  switch (eventType) {
    180    case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
    181      [self moxPostNotification:
    182                NSAccessibilityFocusedUIElementChangedNotification];
    183      MOZ_ASSERT(mGeckoAccessible->IsRemote() ||
    184                     mGeckoAccessible->AsLocal()->IsRoot() ||
    185                     mGeckoAccessible->AsLocal()->AsDoc()->ParentDocument(),
    186                 "Non-root doc without a parent!");
    187      if ((mGeckoAccessible->IsRemote() &&
    188           mGeckoAccessible->AsRemote()->IsDoc() &&
    189           mGeckoAccessible->AsRemote()->AsDoc()->IsTopLevel()) ||
    190          (mGeckoAccessible->IsLocal() &&
    191           !mGeckoAccessible->AsLocal()->IsRoot() &&
    192           mGeckoAccessible->AsLocal()->AsDoc()->ParentDocument() &&
    193           mGeckoAccessible->AsLocal()->AsDoc()->ParentDocument()->IsRoot())) {
    194        // we fire an AXLoadComplete event on top-level documents only
    195        [self moxPostNotification:@"AXLoadComplete"];
    196      } else {
    197        // otherwise the doc belongs to an iframe (IsTopLevelInContentProcess)
    198        // and we fire AXLayoutComplete instead
    199        [self moxPostNotification:@"AXLayoutComplete"];
    200      }
    201      break;
    202  }
    203 
    204  [super handleAccessibleEvent:eventType];
    205 }
    206 
    207 - (NSArray*)rootGroupChildren {
    208  // This method is meant to expose the doc's children to the root group.
    209  return [super moxChildren];
    210 }
    211 
    212 - (NSArray*)moxUnignoredChildren {
    213  if (id rootGroup = [self rootGroup]) {
    214    return @[ rootGroup ];
    215  }
    216 
    217  // There is no root group, expose the children here directly.
    218  return [super moxUnignoredChildren];
    219 }
    220 
    221 - (BOOL)moxBlockSelector:(SEL)selector {
    222  if (selector == @selector(moxSubrole)) {
    223    // Never expose a subrole for a web area.
    224    return YES;
    225  }
    226 
    227  if (selector == @selector(moxElementBusy)) {
    228    // Don't confuse aria-busy with a document's busy state.
    229    return YES;
    230  }
    231 
    232  return [super moxBlockSelector:selector];
    233 }
    234 
    235 - (void)moxPostNotification:(NSString*)notification {
    236  if (![notification isEqualToString:@"AXElementBusyChanged"]) {
    237    // Suppress AXElementBusyChanged since it uses gecko's BUSY state
    238    // to tell VoiceOver about aria-busy changes. We use that state
    239    // differently in documents.
    240    [super moxPostNotification:notification];
    241  }
    242 }
    243 
    244 - (id)rootGroup {
    245  if (mRole == roles::DOCUMENT) {
    246    // We only need a root group if our document has a role other than the
    247    // default document role
    248    return nil;
    249  }
    250 
    251  if (!mRootGroup) {
    252    mRootGroup = [[MOXRootGroup alloc] initWithParent:self];
    253  }
    254 
    255  return mRootGroup;
    256 }
    257 
    258 - (void)expire {
    259  [mRootGroup expire];
    260  [super expire];
    261 }
    262 
    263 - (void)dealloc {
    264  // This object can only be dealoced after the gecko accessible wrapper
    265  // reference is released, and that happens after expire is called.
    266  MOZ_ASSERT([self isExpired]);
    267  [mRootGroup release];
    268 
    269  [super dealloc];
    270 }
    271 
    272 @end