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 "mozAccessible.h"
9 #include "MOXAccessibleBase.h"
13 #import "MOXSearchInfo.h"
14 #import "MOXTextMarkerDelegate.h"
15 #import "MOXWebAreaAccessible.h"
16 #import "mozTextAccessible.h"
17 #import "mozRootAccessible.h"
19 #include "LocalAccessible-inl.h"
20 #include "nsAccUtils.h"
21 #include "DocAccessibleParent.h"
23 #include "mozilla/a11y/Role.h"
24 #include "RootAccessible.h"
25 #include "mozilla/a11y/PDocAccessible.h"
26 #include "mozilla/dom/BrowserParent.h"
27 #include "OuterDocAccessible.h"
28 #include "nsChildView.h"
29 #include "xpcAccessibleMacInterface.h"
32 #include "nsCocoaUtils.h"
34 #include "nsObjCExceptions.h"
35 #include "nsWhitespaceTokenizer.h"
38 using namespace mozilla;
39 using namespace mozilla::a11y;
43 @interface mozAccessible ()
44 - (BOOL)providesLabelNotTitle;
46 - (void)maybePostLiveRegionChanged;
47 - (void)maybePostA11yUtilNotification;
50 @implementation mozAccessible
52 - (id)initWithAccessible:(Accessible*)aAcc {
53 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
54 MOZ_ASSERT(aAcc, "Cannot init mozAccessible with null");
55 if ((self = [super init])) {
56 mGeckoAccessible = aAcc;
62 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
66 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
70 NS_OBJC_END_TRY_IGNORE_BLOCK;
73 #pragma mark - mozAccessible widget
75 - (BOOL)hasRepresentedView {
79 - (id)representedView {
89 - (BOOL)moxIgnoreWithParent:(mozAccessible*)parent {
90 if (LocalAccessible* acc = mGeckoAccessible->AsLocal()) {
91 if (acc->IsContent() && acc->GetContent()->IsXULElement()) {
92 if (acc->VisibilityState() & states::INVISIBLE) {
98 return [parent moxIgnoreChild:self];
101 - (BOOL)moxIgnoreChild:(mozAccessible*)child {
102 return nsAccUtils::MustPrune(mGeckoAccessible);
105 - (id)childAt:(uint32_t)i {
106 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
108 Accessible* child = mGeckoAccessible->ChildAt(i);
109 return child ? GetNativeFromGeckoAccessible(child) : nil;
111 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
115 return mGeckoAccessible->State();
118 - (uint64_t)stateWithMask:(uint64_t)mask {
119 return [self state] & mask;
122 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
123 if (state == states::BUSY) {
124 [self moxPostNotification:@"AXElementBusyChanged"];
128 - (BOOL)providesLabelNotTitle {
129 // These accessible types are the exception to the rule of label vs. title:
130 // They may be named explicitly, but they still provide a label not a title.
131 return mRole == roles::GROUPING || mRole == roles::RADIO_GROUP ||
132 mRole == roles::FIGURE || mRole == roles::GRAPHIC ||
133 mRole == roles::DOCUMENT || mRole == roles::OUTLINE ||
134 mRole == roles::ARTICLE || mRole == roles::ENTRY ||
135 mRole == roles::SPINBUTTON;
138 - (mozilla::a11y::Accessible*)geckoAccessible {
139 return mGeckoAccessible;
142 #pragma mark - MOXAccessible protocol
144 - (BOOL)moxBlockSelector:(SEL)selector {
145 if (selector == @selector(moxPerformPress)) {
146 uint8_t actionCount = mGeckoAccessible->ActionCount();
148 // If we have no action, we don't support press, so return YES.
149 return actionCount == 0;
152 if (selector == @selector(moxSetFocused:)) {
153 return [self stateWithMask:states::FOCUSABLE] == 0;
156 if (selector == @selector(moxARIALive) ||
157 selector == @selector(moxARIAAtomic) ||
158 selector == @selector(moxARIARelevant)) {
159 return ![self moxIsLiveRegion];
162 if (selector == @selector(moxARIAPosInSet) || selector == @selector
164 GroupPos groupPos = mGeckoAccessible->GroupPosition();
165 return groupPos.setSize == 0;
168 if (selector == @selector(moxExpanded)) {
169 return [self stateWithMask:states::EXPANDABLE] == 0;
172 return [super moxBlockSelector:selector];
175 - (id)moxFocusedUIElement {
176 MOZ_ASSERT(mGeckoAccessible);
177 // This only gets queried on the web area or the root group
178 // so just use the doc's focused child instead of trying to get
179 // the focused child of mGeckoAccessible.
180 Accessible* doc = nsAccUtils::DocumentFor(mGeckoAccessible);
181 mozAccessible* focusedChild =
182 GetNativeFromGeckoAccessible(doc->FocusedChild());
184 if ([focusedChild isAccessibilityElement]) {
188 // return ourself if we can't get a native focused child.
192 - (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
193 MOZ_ASSERT(mGeckoAccessible);
195 return [MOXTextMarkerDelegate
196 getOrCreateForDoc:nsAccUtils::DocumentFor(mGeckoAccessible)];
199 - (BOOL)moxIsLiveRegion {
200 return mIsLiveRegion;
203 - (id)moxHitTest:(NSPoint)point {
204 MOZ_ASSERT(mGeckoAccessible);
206 // Convert the given screen-global point in the cocoa coordinate system (with
207 // origin in the bottom-left corner of the screen) into point in the Gecko
208 // coordinate system (with origin in a top-left screen point).
209 NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
211 NSMakePoint(point.x, [mainView frame].size.height - point.y);
212 LayoutDeviceIntPoint geckoPoint = nsCocoaUtils::CocoaPointsToDevPixels(
213 tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView));
215 Accessible* child = mGeckoAccessible->ChildAtPoint(
216 geckoPoint.x, geckoPoint.y, Accessible::EWhichChildAtPoint::DeepestChild);
219 mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
220 return [nativeChild isAccessibilityElement]
222 : [nativeChild moxUnignoredParent];
225 // if we didn't find anything, return ourself or child view.
229 - (id<mozAccessible>)moxParent {
230 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
231 if ([self isExpired]) {
235 Accessible* parent = mGeckoAccessible->Parent();
241 id nativeParent = GetNativeFromGeckoAccessible(parent);
242 if ([nativeParent isKindOfClass:[MOXWebAreaAccessible class]]) {
243 // Before returning a WebArea as parent, check to see if
244 // there is a generated root group that is an intermediate container.
245 if (id<mozAccessible> rootGroup = [nativeParent rootGroup]) {
246 nativeParent = rootGroup;
250 if (!nativeParent && mGeckoAccessible->IsLocal()) {
251 // Return native of root accessible if we have no direct parent.
252 // XXX: need to return a sensible fallback in proxy case as well
253 nativeParent = GetNativeFromGeckoAccessible(
254 mGeckoAccessible->AsLocal()->RootAccessible());
257 return GetObjectOrRepresentedView(nativeParent);
259 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
262 // gets all our native children lazily, including those that are ignored.
263 - (NSArray*)moxChildren {
264 MOZ_ASSERT(mGeckoAccessible);
266 NSMutableArray* children = [[[NSMutableArray alloc]
267 initWithCapacity:mGeckoAccessible->ChildCount()] autorelease];
269 for (uint32_t childIdx = 0; childIdx < mGeckoAccessible->ChildCount();
271 Accessible* child = mGeckoAccessible->ChildAt(childIdx);
272 mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
277 [children addObject:nativeChild];
283 - (NSValue*)moxPosition {
284 CGRect frame = [[self moxFrame] rectValue];
286 return [NSValue valueWithPoint:NSMakePoint(frame.origin.x, frame.origin.y)];
289 - (NSValue*)moxSize {
290 CGRect frame = [[self moxFrame] rectValue];
293 [NSValue valueWithSize:NSMakeSize(frame.size.width, frame.size.height)];
296 - (NSString*)moxRole {
297 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
298 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
300 case roles::geckoRole: \
306 MOZ_ASSERT_UNREACHABLE("Unknown role.");
307 return NSAccessibilityUnknownRole;
313 - (nsStaticAtom*)ARIARole {
314 MOZ_ASSERT(mGeckoAccessible);
316 if (mGeckoAccessible->HasARIARole()) {
317 const nsRoleMapEntry* roleMap = mGeckoAccessible->ARIARoleMap();
318 return roleMap->roleAtom;
321 return nsGkAtoms::_empty;
324 - (NSString*)moxSubrole {
325 MOZ_ASSERT(mGeckoAccessible);
327 // Deal with landmarks first
328 // macOS groups the specific landmark types of DPub ARIA into two broad
329 // categories with corresponding subroles: Navigation and region/container.
330 if (mRole == roles::LANDMARK) {
331 nsAtom* landmark = mGeckoAccessible->LandmarkRole();
332 // HTML Elements treated as landmarks, and ARIA landmarks.
334 if (landmark == nsGkAtoms::banner) return @"AXLandmarkBanner";
335 if (landmark == nsGkAtoms::complementary)
336 return @"AXLandmarkComplementary";
337 if (landmark == nsGkAtoms::contentinfo) return @"AXLandmarkContentInfo";
338 if (landmark == nsGkAtoms::main) return @"AXLandmarkMain";
339 if (landmark == nsGkAtoms::navigation) return @"AXLandmarkNavigation";
340 if (landmark == nsGkAtoms::search) return @"AXLandmarkSearch";
343 // None of the above, so assume DPub ARIA.
344 return @"AXLandmarkRegion";
347 // Now, deal with widget roles
348 nsStaticAtom* roleAtom = nullptr;
350 if (mRole == roles::DIALOG) {
351 roleAtom = [self ARIARole];
353 if (roleAtom == nsGkAtoms::alertdialog) {
354 return @"AXApplicationAlertDialog";
356 if (roleAtom == nsGkAtoms::dialog) {
357 return @"AXApplicationDialog";
361 if (mRole == roles::FORM) {
362 roleAtom = [self ARIARole];
364 if (roleAtom == nsGkAtoms::form) {
365 return @"AXLandmarkForm";
369 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
370 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
372 case roles::geckoRole: \
373 if (![macSubrole isEqualToString:NSAccessibilityUnknownSubrole]) { \
383 // These are special. They map to roles::NOTHING
384 // and are instructed by the ARIA map to use the native host role.
385 roleAtom = [self ARIARole];
387 if (roleAtom == nsGkAtoms::log_) {
388 return @"AXApplicationLog";
391 if (roleAtom == nsGkAtoms::timer) {
392 return @"AXApplicationTimer";
394 // macOS added an AXSubrole value to distinguish generic AXGroup objects
395 // from those which are AXGroups as a result of an explicit ARIA role,
396 // such as the non-landmark, non-listitem text containers in DPub ARIA.
397 if (mRole == roles::FOOTNOTE || mRole == roles::SECTION) {
398 return @"AXApplicationGroup";
401 return NSAccessibilityUnknownSubrole;
406 struct RoleDescrMap {
408 const nsString description;
411 static const RoleDescrMap sRoleDescrMap[] = {
412 {@"AXApplicationAlert", u"alert"_ns},
413 {@"AXApplicationAlertDialog", u"alertDialog"_ns},
414 {@"AXApplicationDialog", u"dialog"_ns},
415 {@"AXApplicationLog", u"log"_ns},
416 {@"AXApplicationMarquee", u"marquee"_ns},
417 {@"AXApplicationStatus", u"status"_ns},
418 {@"AXApplicationTimer", u"timer"_ns},
419 {@"AXContentSeparator", u"separator"_ns},
420 {@"AXDefinition", u"definition"_ns},
421 {@"AXDetails", u"details"_ns},
422 {@"AXDocument", u"document"_ns},
423 {@"AXDocumentArticle", u"article"_ns},
424 {@"AXDocumentMath", u"math"_ns},
425 {@"AXDocumentNote", u"note"_ns},
426 {@"AXLandmarkApplication", u"application"_ns},
427 {@"AXLandmarkBanner", u"banner"_ns},
428 {@"AXLandmarkComplementary", u"complementary"_ns},
429 {@"AXLandmarkContentInfo", u"content"_ns},
430 {@"AXLandmarkMain", u"main"_ns},
431 {@"AXLandmarkNavigation", u"navigation"_ns},
432 {@"AXLandmarkRegion", u"region"_ns},
433 {@"AXLandmarkSearch", u"search"_ns},
434 {@"AXSearchField", u"searchTextField"_ns},
435 {@"AXSummary", u"summary"_ns},
436 {@"AXTabPanel", u"tabPanel"_ns},
437 {@"AXTerm", u"term"_ns},
438 {@"AXUserInterfaceTooltip", u"tooltip"_ns}};
440 struct RoleDescrComparator {
441 const NSString* mRole;
442 explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
443 int operator()(const RoleDescrMap& aEntry) const {
444 return [mRole compare:aEntry.role];
448 - (NSString*)moxRoleDescription {
449 if (NSString* ariaRoleDescription =
450 utils::GetAccAttr(self, nsGkAtoms::aria_roledescription)) {
451 if ([ariaRoleDescription length]) {
452 return ariaRoleDescription;
456 if (mRole == roles::FIGURE) return utils::LocalizedString(u"figure"_ns);
458 if (mRole == roles::HEADING) return utils::LocalizedString(u"heading"_ns);
460 if (mRole == roles::MARK) {
461 return utils::LocalizedString(u"highlight"_ns);
464 NSString* subrole = [self moxSubrole];
468 if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
469 RoleDescrComparator(subrole), &idx)) {
470 return utils::LocalizedString(sRoleDescrMap[idx].description);
474 return NSAccessibilityRoleDescription([self moxRole], subrole);
477 - (NSString*)moxLabel {
478 if ([self isExpired]) {
484 /* If our accessible is:
485 * 1. Named by invisible text, or
486 * 2. Has more than one labeling relation, or
487 * 3. Is a special role defined in providesLabelNotTitle
488 * ... return its name as a label (AXDescription).
490 ENameValueFlag flag = mGeckoAccessible->Name(name);
491 if (flag == eNameFromSubtree) {
495 if (![self providesLabelNotTitle]) {
496 NSArray* relations = [self getRelationsByType:RelationType::LABELLED_BY];
497 if ([relations count] == 1) {
502 return nsCocoaUtils::ToNSString(name);
505 - (NSString*)moxTitle {
506 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
508 // In some special cases we provide the name in the label (AXDescription).
509 if ([self providesLabelNotTitle]) {
514 mGeckoAccessible->Name(title);
515 if (nsCoreUtils::IsWhitespaceString(title)) {
519 return nsCocoaUtils::ToNSString(title);
521 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
525 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
528 mGeckoAccessible->Value(value);
530 return nsCocoaUtils::ToNSString(value);
532 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
535 - (NSString*)moxHelp {
536 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
538 // What needs to go here is actually the accDescription of an item.
539 // The MSAA acc_help method has nothing to do with this one.
540 nsAutoString helpText;
541 mGeckoAccessible->Description(helpText);
543 return nsCocoaUtils::ToNSString(helpText);
545 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
548 - (NSWindow*)moxWindow {
549 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
551 // Get a pointer to the native window (NSWindow) we reside in.
552 NSWindow* nativeWindow = nil;
553 DocAccessible* docAcc = nullptr;
554 if (LocalAccessible* acc = mGeckoAccessible->AsLocal()) {
555 docAcc = acc->Document();
557 RemoteAccessible* proxy = mGeckoAccessible->AsRemote();
558 LocalAccessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
559 if (outerDoc) docAcc = outerDoc->Document();
562 if (docAcc) nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow());
564 MOZ_ASSERT(nativeWindow || gfxPlatform::IsHeadless(),
565 "Couldn't get native window");
568 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
571 - (NSNumber*)moxEnabled {
572 if ([self stateWithMask:states::UNAVAILABLE]) {
576 if (![self isRoot]) {
577 mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
578 if (![parent isRoot]) {
579 return @(![parent disableChild:self]);
586 - (NSNumber*)moxFocused {
587 return @([self stateWithMask:states::FOCUSED] != 0);
590 - (NSNumber*)moxSelected {
594 - (NSNumber*)moxExpanded {
595 return @([self stateWithMask:states::EXPANDED] != 0);
598 - (NSValue*)moxFrame {
599 MOZ_ASSERT(mGeckoAccessible);
601 LayoutDeviceIntRect rect = mGeckoAccessible->Bounds();
602 NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
603 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
606 valueWithRect:NSMakeRect(
607 static_cast<CGFloat>(rect.x) / scaleFactor,
608 [mainView frame].size.height -
609 static_cast<CGFloat>(rect.y + rect.height) /
611 static_cast<CGFloat>(rect.width) / scaleFactor,
612 static_cast<CGFloat>(rect.height) / scaleFactor)];
615 - (NSString*)moxARIACurrent {
616 if (![self stateWithMask:states::CURRENT]) {
620 return utils::GetAccAttr(self, nsGkAtoms::aria_current);
623 - (NSNumber*)moxARIAAtomic {
624 return @(utils::GetAccAttr(self, nsGkAtoms::aria_atomic) != nil);
627 - (NSString*)moxARIALive {
628 return utils::GetAccAttr(self, nsGkAtoms::aria_live);
631 - (NSNumber*)moxARIAPosInSet {
632 GroupPos groupPos = mGeckoAccessible->GroupPosition();
633 return @(groupPos.posInSet);
636 - (NSNumber*)moxARIASetSize {
637 GroupPos groupPos = mGeckoAccessible->GroupPosition();
638 return @(groupPos.setSize);
641 - (NSString*)moxARIARelevant {
642 if (NSString* relevant =
643 utils::GetAccAttr(self, nsGkAtoms::containerRelevant)) {
647 // Default aria-relevant value
648 return @"additions text";
651 - (NSString*)moxPlaceholderValue {
652 // First, check for plaecholder HTML attribute
653 if (NSString* placeholder = utils::GetAccAttr(self, nsGkAtoms::placeholder)) {
657 // If no placeholder HTML attribute, check for the aria version.
658 return utils::GetAccAttr(self, nsGkAtoms::aria_placeholder);
661 - (id)moxTitleUIElement {
662 MOZ_ASSERT(mGeckoAccessible);
664 NSArray* relations = [self getRelationsByType:RelationType::LABELLED_BY];
665 if ([relations count] == 1) {
666 return [relations firstObject];
672 - (NSString*)moxDOMIdentifier {
673 MOZ_ASSERT(mGeckoAccessible);
676 mGeckoAccessible->DOMNodeID(id);
678 return nsCocoaUtils::ToNSString(id);
681 - (NSNumber*)moxRequired {
682 return @([self stateWithMask:states::REQUIRED] != 0);
685 - (NSNumber*)moxElementBusy {
686 return @([self stateWithMask:states::BUSY] != 0);
689 - (NSArray*)moxLinkedUIElements {
690 return [self getRelationsByType:RelationType::FLOWS_TO];
693 - (NSArray*)moxARIAControls {
694 return [self getRelationsByType:RelationType::CONTROLLER_FOR];
697 - (mozAccessible*)topWebArea {
698 Accessible* doc = nsAccUtils::DocumentFor(mGeckoAccessible);
700 if (doc->IsLocal()) {
701 DocAccessible* docAcc = doc->AsLocal()->AsDoc();
702 if (docAcc->DocumentNode()->GetBrowsingContext()->IsTopContent()) {
703 return GetNativeFromGeckoAccessible(docAcc);
706 doc = docAcc->ParentDocument();
708 DocAccessibleParent* docProxy = doc->AsRemote()->AsDoc();
709 if (docProxy->IsTopLevel()) {
710 return GetNativeFromGeckoAccessible(docProxy);
712 doc = docProxy->ParentDoc();
719 - (void)handleRoleChanged:(mozilla::a11y::role)newRole {
723 // For testing purposes
724 [self moxPostNotification:@"AXMozRoleChanged"];
727 - (id)moxEditableAncestor {
728 return [self moxFindAncestor:^BOOL(id moxAcc, BOOL* stop) {
729 return [moxAcc isKindOfClass:[mozTextAccessible class]];
733 - (id)moxHighestEditableAncestor {
734 id highestAncestor = [self moxEditableAncestor];
735 while ([highestAncestor conformsToProtocol:@protocol(MOXAccessible)]) {
736 id ancestorParent = [highestAncestor moxUnignoredParent];
737 if (![ancestorParent conformsToProtocol:@protocol(MOXAccessible)]) {
741 id higherAncestor = [ancestorParent moxEditableAncestor];
743 if (!higherAncestor) {
747 highestAncestor = higherAncestor;
750 return highestAncestor;
753 - (id)moxFocusableAncestor {
754 // XXX: Checking focusable state up the chain can be expensive. For now,
755 // we can just return AXEditableAncestor since the main use case for this
756 // is rich text editing with links.
757 return [self moxEditableAncestor];
760 #ifndef RELEASE_OR_BETA
761 - (NSString*)moxMozDebugDescription {
762 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
764 if (!mGeckoAccessible) {
765 return [NSString stringWithFormat:@"<%@: %p mGeckoAccessible=null>",
766 NSStringFromClass([self class]), self];
769 NSMutableString* domInfo = [NSMutableString string];
770 if (NSString* tagName = utils::GetAccAttr(self, nsGkAtoms::tag)) {
771 [domInfo appendFormat:@" %@", tagName];
772 NSString* domID = [self moxDOMIdentifier];
773 if ([domID length]) {
774 [domInfo appendFormat:@"#%@", domID];
776 if (NSString* className = utils::GetAccAttr(self, nsGkAtoms::_class)) {
779 [className stringByReplacingOccurrencesOfString:@" "
784 return [NSString stringWithFormat:@"<%@: %p %@%@>",
785 NSStringFromClass([self class]), self,
786 [self moxRole], domInfo];
788 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
792 - (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate {
793 // Create our search object and set it up with the searchPredicate
794 // params. The init function does additional parsing. We pass a
795 // reference to the web area to use as a start element if one is not
797 MOXSearchInfo* search =
798 [[[MOXSearchInfo alloc] initWithParameters:searchPredicate
799 andRoot:self] autorelease];
801 return [search performSearch];
804 - (NSNumber*)moxUIElementCountForSearchPredicate:
805 (NSDictionary*)searchPredicate {
807 numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
811 - (void)moxSetFocused:(NSNumber*)focused {
812 MOZ_ASSERT(mGeckoAccessible);
814 if ([focused boolValue]) {
815 mGeckoAccessible->TakeFocus();
819 - (void)moxPerformScrollToVisible {
820 MOZ_ASSERT(mGeckoAccessible);
821 mGeckoAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
824 - (void)moxPerformShowMenu {
825 MOZ_ASSERT(mGeckoAccessible);
827 // We don't need to convert this rect into mac coordinates because the
828 // mouse event synthesizer expects layout (gecko) coordinates.
829 LayoutDeviceIntRect bounds = mGeckoAccessible->Bounds();
831 LocalAccessible* rootAcc = mGeckoAccessible->IsLocal()
832 ? mGeckoAccessible->AsLocal()->RootAccessible()
833 : mGeckoAccessible->AsRemote()
834 ->OuterDocOfRemoteBrowser()
837 GetObjectOrRepresentedView(GetNativeFromGeckoAccessible(rootAcc));
839 LayoutDeviceIntPoint p = LayoutDeviceIntPoint(
840 bounds.X() + (bounds.Width() / 2), bounds.Y() + (bounds.Height() / 2));
841 nsIWidget* widget = [objOrView widget];
842 widget->SynthesizeNativeMouseEvent(
843 p, nsIWidget::NativeMouseMessage::ButtonDown, MouseButton::eSecondary,
844 nsIWidget::Modifiers::NO_MODIFIERS, nullptr);
847 - (void)moxPerformPress {
848 MOZ_ASSERT(mGeckoAccessible);
850 mGeckoAccessible->DoAction(0);
855 - (BOOL)disableChild:(mozAccessible*)child {
859 - (void)maybePostLiveRegionChanged {
860 id<MOXAccessible> liveRegion =
861 [self moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
862 return [moxAcc moxIsLiveRegion];
866 [liveRegion moxPostNotification:@"AXLiveRegionChanged"];
870 - (void)maybePostA11yUtilNotification {
871 MOZ_ASSERT(mGeckoAccessible);
872 // Sometimes we use a special live region to make announcements to the user.
873 // This region is a child of the root document, but doesn't contain any
874 // content. If we try to fire regular AXLiveRegion changed events through it,
875 // VoiceOver clips the notifications because it (rightfully) doesn't detect
876 // focus within the region. We get around this by firing an
877 // AXAnnouncementRequested notification here instead.
878 // Verify we're trying to send a notification for the a11yUtils alert (and not
879 // a random acc with the same ID) by checking:
880 // - The gecko acc is local, our a11y-announcement lives in browser.xhtml
881 // - The ID of the gecko acc is "a11y-announcement"
882 // - The native acc is a direct descendent of the root
883 if (mGeckoAccessible->IsLocal() &&
884 [[self moxDOMIdentifier] isEqualToString:@"a11y-announcement"] &&
885 [[self moxParent] isKindOfClass:[mozRootAccessible class]]) {
886 // Our actual announcement should be stored as a child of the alert,
887 // so we verify a child exists, and then query that child below.
888 NSArray* children = [self moxChildren];
889 MOZ_ASSERT([children count] == 1 && children[0],
890 "A11yUtil event received, but no announcement found?");
892 mozAccessible* announcement = children[0];
894 if ([announcement providesLabelNotTitle]) {
895 key = [announcement moxLabel];
897 key = [announcement moxTitle];
900 NSDictionary* info = @{
901 NSAccessibilityAnnouncementKey : key ? key : @(""),
902 NSAccessibilityPriorityKey : @(NSAccessibilityPriorityMedium)
905 id window = [self moxWindow];
907 // This sends events via nsIObserverService to be consumed by our
908 // mochitests. Normally we'd fire these events through moxPostNotification
909 // which takes care of this, but because the window we fetch above isn't
910 // derrived from MOXAccessibleBase, we do this (and post the notification)
912 xpcAccessibleMacEvent::FireEvent(
913 window, NSAccessibilityAnnouncementRequestedNotification, info);
914 NSAccessibilityPostNotificationWithUserInfo(
915 window, NSAccessibilityAnnouncementRequestedNotification, info);
919 - (NSArray<mozAccessible*>*)getRelationsByType:(RelationType)relationType {
920 NSMutableArray<mozAccessible*>* relations =
921 [[[NSMutableArray alloc] init] autorelease];
922 Relation rel = mGeckoAccessible->RelationByType(relationType);
923 while (Accessible* relAcc = rel.Next()) {
924 if (mozAccessible* relNative = GetNativeFromGeckoAccessible(relAcc)) {
925 [relations addObject:relNative];
932 - (void)handleAccessibleTextChangeEvent:(NSString*)change
933 inserted:(BOOL)isInserted
934 inContainer:(Accessible*)container
938 - (void)handleAccessibleEvent:(uint32_t)eventType {
940 case nsIAccessibleEvent::EVENT_ALERT:
941 [self maybePostA11yUtilNotification];
943 case nsIAccessibleEvent::EVENT_FOCUS:
944 [self moxPostNotification:
945 NSAccessibilityFocusedUIElementChangedNotification];
947 case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
948 [self moxPostNotification:@"AXMenuOpened"];
950 case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
951 [self moxPostNotification:@"AXMenuClosed"];
953 case nsIAccessibleEvent::EVENT_SELECTION:
954 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
955 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
956 case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
957 [self moxPostNotification:
958 NSAccessibilitySelectedChildrenChangedNotification];
960 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
961 if (![self stateWithMask:states::SELECTABLE_TEXT]) {
964 // We consider any caret move event to be a selected text change event.
965 // So dispatching an event for EVENT_TEXT_SELECTION_CHANGED would be
967 MOXTextMarkerDelegate* delegate =
968 static_cast<MOXTextMarkerDelegate*>([self moxTextMarkerDelegate]);
969 NSMutableDictionary* userInfo =
970 [[[delegate selectionChangeInfo] mutableCopy] autorelease];
971 userInfo[@"AXTextChangeElement"] = self;
973 mozAccessible* webArea = [self topWebArea];
975 moxPostNotification:NSAccessibilitySelectedTextChangedNotification
976 withUserInfo:userInfo];
977 [self moxPostNotification:NSAccessibilitySelectedTextChangedNotification
978 withUserInfo:userInfo];
981 case nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED:
982 mIsLiveRegion = true;
983 [self moxPostNotification:@"AXLiveRegionCreated"];
985 case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED:
986 mIsLiveRegion = false;
988 case nsIAccessibleEvent::EVENT_REORDER:
989 [self maybePostLiveRegionChanged];
991 case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
992 if (![self providesLabelNotTitle]) {
993 [self moxPostNotification:NSAccessibilityTitleChangedNotification];
995 [self maybePostLiveRegionChanged];
1002 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1004 mGeckoAccessible = nullptr;
1006 [self moxPostNotification:NSAccessibilityUIElementDestroyedNotification];
1008 NS_OBJC_END_TRY_IGNORE_BLOCK;
1012 return !mGeckoAccessible;