Bug 1648429 [wpt PR 24338] - Python 3: port tests in service-workers [part 5], a...
[gecko.git] / accessible / mac / mozAccessible.mm
blob63b9e313656fee14e034c32e48892b0e969d5eed
1 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #import "mozAccessible.h"
8 #import "MacUtils.h"
9 #import "mozView.h"
11 #include "Accessible-inl.h"
12 #include "nsAccUtils.h"
13 #include "nsIPersistentProperties2.h"
14 #include "DocAccessibleParent.h"
15 #include "Relation.h"
16 #include "Role.h"
17 #include "RootAccessible.h"
18 #include "TableAccessible.h"
19 #include "TableCellAccessible.h"
20 #include "mozilla/a11y/PDocAccessible.h"
21 #include "mozilla/dom/BrowserParent.h"
22 #include "OuterDocAccessible.h"
23 #include "nsChildView.h"
25 #include "nsRect.h"
26 #include "nsCocoaUtils.h"
27 #include "nsCoord.h"
28 #include "nsObjCExceptions.h"
29 #include "nsWhitespaceTokenizer.h"
30 #include <prdtoa.h>
32 using namespace mozilla;
33 using namespace mozilla::a11y;
35 #pragma mark -
37 @interface mozAccessible ()
38 - (BOOL)providesLabelNotTitle;
39 @end
41 @implementation mozAccessible
43 - (id)initWithAccessible:(AccessibleOrProxy)aAccOrProxy {
44   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
45   MOZ_ASSERT(!aAccOrProxy.IsNull(), "Cannot init mozAccessible with null");
46   if ((self = [super init])) {
47     mGeckoAccessible = aAccOrProxy;
48     mRole = aAccOrProxy.Role();
49   }
51   return self;
53   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
56 - (void)dealloc {
57   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
59   [super dealloc];
61   NS_OBJC_END_TRY_ABORT_BLOCK;
64 #pragma mark - mozAccessible widget
66 - (BOOL)hasRepresentedView {
67   return NO;
70 - (id)representedView {
71   return nil;
74 - (BOOL)isRoot {
75   return NO;
78 #pragma mark -
80 - (BOOL)isAccessibilityElement {
81   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
83   if ([self isExpired]) {
84     return ![self ignoreWithParent:nil];
85   }
87   mozAccessible* parent = nil;
88   AccessibleOrProxy p = mGeckoAccessible.Parent();
90   if (!p.IsNull()) {
91     parent = GetNativeFromGeckoAccessible(p);
92   }
94   return ![self ignoreWithParent:parent];
96   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
99 - (BOOL)ignoreWithParent:(mozAccessible*)parent {
100   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
101     if (acc->IsContent() && acc->GetContent()->IsXULElement()) {
102       if (acc->VisibilityState() & states::INVISIBLE) {
103         return YES;
104       }
105     }
106   }
108   return [parent ignoreChild:self];
111 - (BOOL)ignoreChild:(mozAccessible*)child {
112   return NO;
115 - (id)childAt:(uint32_t)i {
116   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
118   AccessibleOrProxy child = mGeckoAccessible.ChildAt(i);
119   return !child.IsNull() ? GetNativeFromGeckoAccessible(child) : nil;
121   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
124 static const uint64_t kCachedStates = states::CHECKED | states::PRESSED | states::MIXED |
125                                       states::EXPANDED | states::CURRENT | states::SELECTED |
126                                       states::TRAVERSED | states::LINKED | states::HASPOPUP;
127 static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63;
129 - (uint64_t)state {
130   uint64_t state = 0;
132   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
133     state = acc->State();
134   }
136   if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
137     state = proxy->State();
138   }
140   if (!(mCachedState & kCacheInitialized)) {
141     mCachedState = state & kCachedStates;
142     mCachedState |= kCacheInitialized;
143   }
145   return state;
148 - (uint64_t)stateWithMask:(uint64_t)mask {
149   if ((mask & kCachedStates) == mask && (mCachedState & kCacheInitialized) != 0) {
150     return mCachedState & mask;
151   }
153   return [self state] & mask;
156 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
157   if ((state & kCachedStates) == 0) {
158     return;
159   }
161   if (!(mCachedState & kCacheInitialized)) {
162     [self state];
163     return;
164   }
166   if (enabled) {
167     mCachedState |= state;
168   } else {
169     mCachedState &= ~state;
170   }
173 - (void)invalidateState {
174   mCachedState = 0;
177 - (BOOL)providesLabelNotTitle {
178   // These accessible types are the exception to the rule of label vs. title:
179   // They may be named explicitly, but they still provide a label not a title.
180   return mRole == roles::GROUPING || mRole == roles::RADIO_GROUP || mRole == roles::FIGURE ||
181          mRole == roles::GRAPHIC || mRole == roles::DOCUMENT;
184 - (mozilla::a11y::AccessibleOrProxy)geckoAccessible {
185   return mGeckoAccessible;
188 #pragma mark - MOXAccessible protocol
190 - (BOOL)moxBlockSelector:(SEL)selector {
191   if (selector == @selector(moxPerformPress)) {
192     uint8_t actionCount = mGeckoAccessible.IsAccessible()
193                               ? mGeckoAccessible.AsAccessible()->ActionCount()
194                               : mGeckoAccessible.AsProxy()->ActionCount();
196     // If we have no action, we don't support press, so return YES.
197     return actionCount == 0;
198   }
200   if (selector == @selector(moxSetFocused:)) {
201     return [self stateWithMask:states::FOCUSABLE] == 0;
202   }
204   return [super moxBlockSelector:selector];
207 - (id)moxFocusedUIElement {
208   MOZ_ASSERT(!mGeckoAccessible.IsNull());
210   Accessible* acc = mGeckoAccessible.AsAccessible();
211   ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
213   mozAccessible* focusedChild = nil;
214   if (acc) {
215     Accessible* focusedGeckoChild = acc->FocusedChild();
216     if (focusedGeckoChild) {
217       focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
218     } else {
219       dom::BrowserParent* browser = dom::BrowserParent::GetFocused();
220       if (browser) {
221         a11y::DocAccessibleParent* proxyDoc = browser->GetTopLevelDocAccessible();
222         if (proxyDoc) {
223           mozAccessible* nativeRemoteChild = GetNativeFromGeckoAccessible(proxyDoc);
224           return [nativeRemoteChild accessibilityFocusedUIElement];
225         }
226       }
227     }
228   } else if (proxy) {
229     ProxyAccessible* focusedGeckoChild = proxy->FocusedChild();
230     if (focusedGeckoChild) {
231       focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
232     }
233   }
235   if ([focusedChild isAccessibilityElement]) {
236     return focusedChild;
237   }
239   // return ourself if we can't get a native focused child.
240   return self;
243 - (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
244   MOZ_ASSERT(!mGeckoAccessible.IsNull());
246   if (mGeckoAccessible.IsAccessible()) {
247     return [MOXTextMarkerDelegate getOrCreateForDoc:mGeckoAccessible.AsAccessible()->Document()];
248   }
250   return [MOXTextMarkerDelegate getOrCreateForDoc:mGeckoAccessible.AsProxy()->Document()];
253 - (id)moxHitTest:(NSPoint)point {
254   MOZ_ASSERT(!mGeckoAccessible.IsNull());
256   // Convert the given screen-global point in the cocoa coordinate system (with
257   // origin in the bottom-left corner of the screen) into point in the Gecko
258   // coordinate system (with origin in a top-left screen point).
259   NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
260   NSPoint tmpPoint = NSMakePoint(point.x, [mainView frame].size.height - point.y);
261   LayoutDeviceIntPoint geckoPoint =
262       nsCocoaUtils::CocoaPointsToDevPixels(tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView));
264   AccessibleOrProxy child =
265       mGeckoAccessible.ChildAtPoint(geckoPoint.x, geckoPoint.y, Accessible::eDeepestChild);
267   if (!child.IsNull()) {
268     mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
269     return [nativeChild isAccessibilityElement] ? nativeChild : [nativeChild moxParent];
270   }
272   // if we didn't find anything, return ourself or child view.
273   return self;
276 - (id<mozAccessible>)moxParent {
277   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
278   if ([self isExpired]) {
279     return nil;
280   }
282   AccessibleOrProxy parent = mGeckoAccessible.Parent();
284   if (parent.IsNull()) {
285     return nil;
286   }
288   id nativeParent = GetNativeFromGeckoAccessible(parent);
289   if (!nativeParent && mGeckoAccessible.IsAccessible()) {
290     // Return native of root accessible if we have no direct parent.
291     // XXX: need to return a sensible fallback in proxy case as well
292     nativeParent = GetNativeFromGeckoAccessible(mGeckoAccessible.AsAccessible()->RootAccessible());
293   }
295   if (![nativeParent isAccessibilityElement]) {
296     nativeParent = [nativeParent moxParent];
297   }
299   return GetObjectOrRepresentedView(nativeParent);
301   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
304 // gets our native children lazily.
305 - (NSArray*)moxChildren {
306   MOZ_ASSERT(!mGeckoAccessible.IsNull());
308   NSMutableArray* children =
309       [[NSMutableArray alloc] initWithCapacity:mGeckoAccessible.ChildCount()];
311   for (uint32_t childIdx = 0; childIdx < mGeckoAccessible.ChildCount(); childIdx++) {
312     AccessibleOrProxy child = mGeckoAccessible.ChildAt(childIdx);
313     mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
314     if (!nativeChild) {
315       continue;
316     }
318     if ([nativeChild ignoreWithParent:self]) {
319       // If this child should be ignored get its unignored children.
320       // This will in turn recurse to any unignored descendants if the
321       // child is ignored.
322       [children addObjectsFromArray:[nativeChild moxChildren]];
323     } else {
324       [children addObject:nativeChild];
325     }
326   }
328   return children;
331 - (NSValue*)moxPosition {
332   MOZ_ASSERT(!mGeckoAccessible.IsNull());
334   nsIntRect rect = mGeckoAccessible.IsAccessible() ? mGeckoAccessible.AsAccessible()->Bounds()
335                                                    : mGeckoAccessible.AsProxy()->Bounds();
337   NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
338   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
339   NSPoint p = NSMakePoint(
340       static_cast<CGFloat>(rect.x) / scaleFactor,
341       [mainView frame].size.height - static_cast<CGFloat>(rect.y + rect.height) / scaleFactor);
343   return [NSValue valueWithPoint:p];
346 - (NSValue*)moxSize {
347   MOZ_ASSERT(!mGeckoAccessible.IsNull());
349   nsIntRect rect = mGeckoAccessible.IsAccessible() ? mGeckoAccessible.AsAccessible()->Bounds()
350                                                    : mGeckoAccessible.AsProxy()->Bounds();
352   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]);
353   return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor,
354                                            static_cast<CGFloat>(rect.height) / scaleFactor)];
357 - (NSString*)moxRole {
358 #define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, ia2Role, androidClass, \
359              nameRule)                                                                             \
360   case roles::geckoRole:                                                                           \
361     return macRole;
363   switch (mRole) {
364 #include "RoleMap.h"
365     default:
366       MOZ_ASSERT_UNREACHABLE("Unknown role.");
367       return NSAccessibilityUnknownRole;
368   }
370 #undef ROLE
373 - (NSString*)moxSubrole {
374   MOZ_ASSERT(!mGeckoAccessible.IsNull());
376   Accessible* acc = mGeckoAccessible.AsAccessible();
377   ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
379   // Deal with landmarks first
380   // macOS groups the specific landmark types of DPub ARIA into two broad
381   // categories with corresponding subroles: Navigation and region/container.
382   if (mRole == roles::LANDMARK) {
383     nsAtom* landmark = acc ? acc->LandmarkRole() : proxy->LandmarkRole();
384     // HTML Elements treated as landmarks, and ARIA landmarks.
385     if (landmark) {
386       if (landmark == nsGkAtoms::banner) return @"AXLandmarkBanner";
387       if (landmark == nsGkAtoms::complementary) return @"AXLandmarkComplementary";
388       if (landmark == nsGkAtoms::contentinfo) return @"AXLandmarkContentInfo";
389       if (landmark == nsGkAtoms::main) return @"AXLandmarkMain";
390       if (landmark == nsGkAtoms::navigation) return @"AXLandmarkNavigation";
391       if (landmark == nsGkAtoms::search) return @"AXLandmarkSearch";
392     }
394     // None of the above, so assume DPub ARIA.
395     return @"AXLandmarkRegion";
396   }
398   // Now, deal with widget roles
399   nsStaticAtom* roleAtom = nullptr;
401   if (mRole == roles::DIALOG) {
402     if (acc && acc->HasARIARole()) {
403       const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
404       roleAtom = roleMap->roleAtom;
405     } else if (proxy) {
406       roleAtom = proxy->ARIARoleAtom();
407     }
409     if (roleAtom) {
410       if (roleAtom == nsGkAtoms::alertdialog) {
411         return @"AXApplicationAlertDialog";
412       }
413       if (roleAtom == nsGkAtoms::dialog) {
414         return @"AXApplicationDialog";
415       }
416     }
417   }
419   if (mRole == roles::FORM) {
420     // This only gets exposed as a landmark if the role comes from ARIA.
421     if (acc && acc->HasARIARole()) {
422       const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
423       roleAtom = roleMap->roleAtom;
424     } else if (proxy) {
425       roleAtom = proxy->ARIARoleAtom();
426     }
428     if (roleAtom && roleAtom == nsGkAtoms::form) {
429       return @"AXLandmarkForm";
430     }
431   }
433 #define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, ia2Role, androidClass, \
434              nameRule)                                                                             \
435   case roles::geckoRole:                                                                           \
436     if (![macSubrole isEqualToString:NSAccessibilityUnknownSubrole]) {                             \
437       return macSubrole;                                                                           \
438     } else {                                                                                       \
439       break;                                                                                       \
440     }
442   switch (mRole) {
443 #include "RoleMap.h"
444   }
446   // These are special. They map to roles::NOTHING
447   // and are instructed by the ARIA map to use the native host role.
448   if (acc && acc->HasARIARole()) {
449     const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
450     roleAtom = roleMap->roleAtom;
451   }
452   if (proxy) roleAtom = proxy->ARIARoleAtom();
454   if (roleAtom) {
455     if (roleAtom == nsGkAtoms::log_) return @"AXApplicationLog";
456     if (roleAtom == nsGkAtoms::timer) return @"AXApplicationTimer";
457     // macOS added an AXSubrole value to distinguish generic AXGroup objects
458     // from those which are AXGroups as a result of an explicit ARIA role,
459     // such as the non-landmark, non-listitem text containers in DPub ARIA.
460     if (mRole == roles::FOOTNOTE || mRole == roles::SECTION) {
461       return @"AXApplicationGroup";
462     }
463   }
465   return NSAccessibilityUnknownSubrole;
467 #undef ROLE
470 struct RoleDescrMap {
471   NSString* role;
472   const nsString description;
475 static const RoleDescrMap sRoleDescrMap[] = {{@"AXApplicationAlert", u"alert"_ns},
476                                              {@"AXApplicationAlertDialog", u"alertDialog"_ns},
477                                              {@"AXApplicationDialog", u"dialog"_ns},
478                                              {@"AXApplicationLog", u"log"_ns},
479                                              {@"AXApplicationMarquee", u"marquee"_ns},
480                                              {@"AXApplicationStatus", u"status"_ns},
481                                              {@"AXApplicationTimer", u"timer"_ns},
482                                              {@"AXContentSeparator", u"separator"_ns},
483                                              {@"AXDefinition", u"definition"_ns},
484                                              {@"AXDetails", u"details"_ns},
485                                              {@"AXDocument", u"document"_ns},
486                                              {@"AXDocumentArticle", u"article"_ns},
487                                              {@"AXDocumentMath", u"math"_ns},
488                                              {@"AXDocumentNote", u"note"_ns},
489                                              {@"AXLandmarkApplication", u"application"_ns},
490                                              {@"AXLandmarkBanner", u"banner"_ns},
491                                              {@"AXLandmarkComplementary", u"complementary"_ns},
492                                              {@"AXLandmarkContentInfo", u"content"_ns},
493                                              {@"AXLandmarkMain", u"main"_ns},
494                                              {@"AXLandmarkNavigation", u"navigation"_ns},
495                                              {@"AXLandmarkRegion", u"region"_ns},
496                                              {@"AXLandmarkSearch", u"search"_ns},
497                                              {@"AXSearchField", u"searchTextField"_ns},
498                                              {@"AXSummary", u"summary"_ns},
499                                              {@"AXTabPanel", u"tabPanel"_ns},
500                                              {@"AXTerm", u"term"_ns},
501                                              {@"AXUserInterfaceTooltip", u"tooltip"_ns}};
503 struct RoleDescrComparator {
504   const NSString* mRole;
505   explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
506   int operator()(const RoleDescrMap& aEntry) const { return [mRole compare:aEntry.role]; }
509 - (NSString*)moxRoleDescription {
510   if (mRole == roles::DOCUMENT) return utils::LocalizedString(u"htmlContent"_ns);
512   if (mRole == roles::FIGURE) return utils::LocalizedString(u"figure"_ns);
514   if (mRole == roles::HEADING) return utils::LocalizedString(u"heading"_ns);
516   if (mRole == roles::MARK) {
517     return utils::LocalizedString(u"highlight"_ns);
518   }
520   NSString* subrole = [self moxSubrole];
522   if (subrole) {
523     size_t idx = 0;
524     if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap), RoleDescrComparator(subrole),
525                        &idx)) {
526       return utils::LocalizedString(sRoleDescrMap[idx].description);
527     }
528   }
530   return NSAccessibilityRoleDescription([self moxRole], subrole);
533 - (NSString*)moxLabel {
534   if ([self isExpired]) {
535     return nil;
536   }
538   Accessible* acc = mGeckoAccessible.AsAccessible();
539   ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
540   nsAutoString name;
542   /* If our accessible is:
543    * 1. Named by invisible text, or
544    * 2. Has more than one labeling relation, or
545    * 3. Is a special role defined in providesLabelNotTitle
546    *   ... return its name as a label (AXDescription).
547    */
548   if (acc) {
549     ENameValueFlag flag = acc->Name(name);
550     if (flag == eNameFromSubtree) {
551       return nil;
552     }
554     if (![self providesLabelNotTitle]) {
555       Relation rel = acc->RelationByType(RelationType::LABELLED_BY);
556       if (rel.Next() && !rel.Next()) {
557         return nil;
558       }
559     }
560   } else if (proxy) {
561     uint32_t flag = proxy->Name(name);
562     if (flag == eNameFromSubtree) {
563       return nil;
564     }
566     if (![self providesLabelNotTitle]) {
567       nsTArray<ProxyAccessible*> rels = proxy->RelationByType(RelationType::LABELLED_BY);
568       if (rels.Length() == 1) {
569         return nil;
570       }
571     }
572   }
574   return nsCocoaUtils::ToNSString(name);
577 - (NSString*)moxTitle {
578   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
580   // In some special cases we provide the name in the label (AXDescription).
581   if ([self providesLabelNotTitle]) {
582     return nil;
583   }
585   nsAutoString title;
586   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
587     acc->Name(title);
588   } else {
589     mGeckoAccessible.AsProxy()->Name(title);
590   }
592   return nsCocoaUtils::ToNSString(title);
594   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
597 - (id)moxValue {
598   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
600   nsAutoString value;
601   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
602     acc->Value(value);
603   } else {
604     mGeckoAccessible.AsProxy()->Value(value);
605   }
607   return nsCocoaUtils::ToNSString(value);
609   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
612 - (NSString*)moxHelp {
613   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
615   // What needs to go here is actually the accDescription of an item.
616   // The MSAA acc_help method has nothing to do with this one.
617   nsAutoString helpText;
618   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
619     acc->Description(helpText);
620   } else {
621     mGeckoAccessible.AsProxy()->Description(helpText);
622   }
624   return nsCocoaUtils::ToNSString(helpText);
626   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
629 - (NSWindow*)moxWindow {
630   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
632   // Get a pointer to the native window (NSWindow) we reside in.
633   NSWindow* nativeWindow = nil;
634   DocAccessible* docAcc = nullptr;
635   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
636     docAcc = acc->Document();
637   } else {
638     ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
639     Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
640     if (outerDoc) docAcc = outerDoc->Document();
641   }
643   if (docAcc) nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow());
645   MOZ_ASSERT(nativeWindow, "Couldn't get native window");
646   return nativeWindow;
648   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
651 - (NSNumber*)moxEnabled {
652   if ([self stateWithMask:states::UNAVAILABLE]) {
653     return @NO;
654   }
656   if (![self isRoot]) {
657     mozAccessible* parent = (mozAccessible*)[self moxParent];
658     if (![parent isRoot]) {
659       return @(![parent disableChild:self]);
660     }
661   }
663   return @YES;
666 - (NSNumber*)moxFocused {
667   return @([self stateWithMask:states::FOCUSED] != 0);
670 - (NSNumber*)moxSelected {
671   return @NO;
674 - (NSString*)moxARIACurrent {
675   if (![self stateWithMask:states::CURRENT]) {
676     return nil;
677   }
679   return utils::GetAccAttr(self, "current");
682 - (id)moxTitleUIElement {
683   MOZ_ASSERT(!mGeckoAccessible.IsNull());
685   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
686     Relation rel = acc->RelationByType(RelationType::LABELLED_BY);
687     Accessible* tempAcc = rel.Next();
688     if (tempAcc && !rel.Next()) {
689       mozAccessible* label = GetNativeFromGeckoAccessible(tempAcc);
690       return [label isAccessibilityElement] ? label : nil;
691     }
693     return nil;
694   }
696   ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
697   nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY);
698   ProxyAccessible* tempProxy = rel.SafeElementAt(0);
699   if (tempProxy && rel.Length() <= 1) {
700     mozAccessible* label = GetNativeFromGeckoAccessible(tempProxy);
701     return [label isAccessibilityElement] ? label : nil;
702   }
704   return nil;
707 - (NSString*)moxDOMIdentifier {
708   MOZ_ASSERT(!mGeckoAccessible.IsNull());
710   nsAutoString id;
711   if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
712     if (acc->GetContent()) {
713       nsCoreUtils::GetID(acc->GetContent(), id);
714     }
715   } else {
716     mGeckoAccessible.AsProxy()->DOMNodeID(id);
717   }
719   return nsCocoaUtils::ToNSString(id);
722 - (NSNumber*)moxRequired {
723   return @([self stateWithMask:states::REQUIRED] != 0);
726 - (void)moxSetFocused:(NSNumber*)focused {
727   MOZ_ASSERT(!mGeckoAccessible.IsNull());
729   if ([focused boolValue]) {
730     if (mGeckoAccessible.IsAccessible()) {
731       mGeckoAccessible.AsAccessible()->TakeFocus();
732     } else {
733       mGeckoAccessible.AsProxy()->TakeFocus();
734     }
735   }
738 - (void)moxPerformScrollToVisible {
739   MOZ_ASSERT(!mGeckoAccessible.IsNull());
741   if (mGeckoAccessible.IsAccessible()) {
742     // Need strong ref because of MOZ_CAN_RUN_SCRIPT
743     RefPtr<Accessible> acc = mGeckoAccessible.AsAccessible();
744     acc->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
745   } else {
746     mGeckoAccessible.AsProxy()->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
747   }
750 - (void)moxPerformShowMenu {
751   MOZ_ASSERT(!mGeckoAccessible.IsNull());
753   nsIntRect bounds = mGeckoAccessible.IsAccessible() ? mGeckoAccessible.AsAccessible()->Bounds()
754                                                      : mGeckoAccessible.AsProxy()->Bounds();
755   // We don't need to convert this rect into mac coordinates because the
756   // mouse event synthesizer expects layout (gecko) coordinates.
757   LayoutDeviceIntRect geckoRect = LayoutDeviceIntRect::FromUnknownRect(bounds);
759   Accessible* rootAcc =
760       mGeckoAccessible.IsAccessible()
761           ? mGeckoAccessible.AsAccessible()->RootAccessible()
762           : mGeckoAccessible.AsProxy()->OuterDocOfRemoteBrowser()->RootAccessible();
763   id objOrView = GetObjectOrRepresentedView(GetNativeFromGeckoAccessible(rootAcc));
765   LayoutDeviceIntPoint p = LayoutDeviceIntPoint(geckoRect.X() + (geckoRect.Width() / 2),
766                                                 geckoRect.Y() + (geckoRect.Height() / 2));
767   nsIWidget* widget = [objOrView widget];
768   // XXX: NSRightMouseDown is depreciated in 10.12, should be
769   // changed to NSEventTypeRightMouseDown after refactoring.
770   widget->SynthesizeNativeMouseEvent(p, NSRightMouseDown, 0, nullptr);
773 - (void)moxPerformPress {
774   MOZ_ASSERT(!mGeckoAccessible.IsNull());
776   if (mGeckoAccessible.IsAccessible()) {
777     mGeckoAccessible.AsAccessible()->DoAction(0);
778   } else {
779     mGeckoAccessible.AsProxy()->DoAction(0);
780   }
782   // Activating accessible may alter its state.
783   [self invalidateState];
786 #pragma mark -
788 // objc-style description (from NSObject); not to be confused with the accessible description above.
789 - (NSString*)description {
790   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
792   return [NSString stringWithFormat:@"(%p) %@", self, [self moxRole]];
794   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
797 - (BOOL)disableChild:(mozAccessible*)child {
798   return NO;
801 - (void)handleAccessibleEvent:(uint32_t)eventType {
802   switch (eventType) {
803     case nsIAccessibleEvent::EVENT_FOCUS:
804       [self moxPostNotification:NSAccessibilityFocusedUIElementChangedNotification];
805       break;
806     case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
807       [self moxPostNotification:@"AXMenuOpened"];
808       break;
809     case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
810       [self moxPostNotification:@"AXMenuClosed"];
811       break;
812     case nsIAccessibleEvent::EVENT_SELECTION:
813     case nsIAccessibleEvent::EVENT_SELECTION_ADD:
814     case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
815     case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
816       [self moxPostNotification:NSAccessibilitySelectedChildrenChangedNotification];
817       break;
818   }
821 - (void)expire {
822   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
824   [self invalidateState];
826   mGeckoAccessible.SetBits(0);
828   [self moxPostNotification:NSAccessibilityUIElementDestroyedNotification];
830   NS_OBJC_END_TRY_ABORT_BLOCK;
833 - (BOOL)isExpired {
834   return !mGeckoAccessible.AsAccessible() && !mGeckoAccessible.AsProxy();
837 @end