no bug - Correct some typos in the comments. a=typo-fix
[gecko.git] / accessible / mac / mozSelectableElements.mm
blob348221ef1d0fcadbff43d7b12a13a8110a894bda
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/. */
8 #import "mozSelectableElements.h"
9 #import "MOXWebAreaAccessible.h"
10 #import "MacUtils.h"
11 #include "LocalAccessible-inl.h"
12 #include "nsCocoaUtils.h"
14 using namespace mozilla::a11y;
16 @implementation mozSelectableAccessible
18 /**
19  * Return the mozAccessibles that are selectable.
20  */
21 - (NSArray*)selectableChildren {
22   NSArray* toFilter;
23   if ([self isKindOfClass:[mozMenuAccessible class]]) {
24     // If we are a menu, our children are only selectable if they are visible
25     // so we filter this array instead of our unignored children list, which may
26     // contain invisible items.
27     toFilter = [static_cast<mozMenuAccessible*>(self) moxVisibleChildren];
28   } else {
29     toFilter = [self moxUnignoredChildren];
30   }
31   return [toFilter
32       filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
33                                                    mozAccessible* child,
34                                                    NSDictionary* bindings) {
35         return [child isKindOfClass:[mozSelectableChildAccessible class]];
36       }]];
39 - (void)moxSetSelectedChildren:(NSArray*)selectedChildren {
40   for (id child in [self selectableChildren]) {
41     BOOL selected =
42         [selectedChildren indexOfObjectIdenticalTo:child] != NSNotFound;
43     [child moxSetSelected:@(selected)];
44   }
47 /**
48  * Return the mozAccessibles that are actually selected.
49  */
50 - (NSArray*)moxSelectedChildren {
51   return [[self selectableChildren]
52       filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
53                                                    mozAccessible* child,
54                                                    NSDictionary* bindings) {
55         // Return mozSelectableChildAccessibles that have are selected (truthy
56         // value).
57         return [[(mozSelectableChildAccessible*)child moxSelected] boolValue];
58       }]];
61 @end
63 @implementation mozSelectableChildAccessible
65 - (NSNumber*)moxSelected {
66   return @([self stateWithMask:states::SELECTED] != 0);
69 - (void)moxSetSelected:(NSNumber*)selected {
70   // Get SELECTABLE and UNAVAILABLE state.
71   uint64_t state =
72       [self stateWithMask:(states::SELECTABLE | states::UNAVAILABLE)];
73   if ((state & states::SELECTABLE) == 0 || (state & states::UNAVAILABLE) != 0) {
74     // The object is either not selectable or is unavailable. Don't do anything.
75     return;
76   }
78   mGeckoAccessible->SetSelected([selected boolValue]);
81 @end
83 @implementation mozTabGroupAccessible
85 - (NSArray*)moxTabs {
86   return [self selectableChildren];
89 - (NSArray*)moxContents {
90   return [self moxUnignoredChildren];
93 - (id)moxValue {
94   // The value of a tab group is its selected child. In the case
95   // of multiple selections this will return the first one.
96   return [[self moxSelectedChildren] firstObject];
99 @end
101 @implementation mozTabAccessible
103 - (NSString*)moxRoleDescription {
104   return utils::LocalizedString(u"tab"_ns);
107 - (id)moxValue {
108   // Retuens 1 if item is selected, 0 if not.
109   return [self moxSelected];
112 @end
114 @implementation mozListboxAccessible
116 - (BOOL)moxIgnoreChild:(mozAccessible*)child {
117   if (!child || child->mRole == roles::GROUPING) {
118     return YES;
119   }
121   return [super moxIgnoreChild:child];
124 - (BOOL)disableChild:(mozAccessible*)child {
125   return ![child isKindOfClass:[mozSelectableChildAccessible class]];
128 - (NSString*)moxOrientation {
129   return NSAccessibilityUnknownOrientationValue;
132 @end
134 @implementation mozOptionAccessible
136 - (NSString*)moxTitle {
137   return @"";
140 - (id)moxValue {
141   // Swap title and value of option so it behaves more like a AXStaticText.
142   return [super moxTitle];
145 @end
147 @implementation mozMenuAccessible
149 - (NSString*)moxTitle {
150   return @"";
153 - (NSString*)moxLabel {
154   return @"";
157 - (BOOL)moxIgnoreWithParent:(mozAccessible*)parent {
158   // This helps us generate the correct moxChildren array for
159   // a sub menu -- that returned array should contain all
160   // menu items, regardless of if they are visible or not.
161   // Because moxChildren does ignore filtering, and because
162   // our base ignore method filters out invisible accessibles,
163   // we override this method.
164   if ([parent isKindOfClass:[MOXWebAreaAccessible class]] ||
165       [parent isKindOfClass:[MOXRootGroup class]]) {
166     // We are a top level menu. Check our visibility the normal way
167     return [super moxIgnoreWithParent:parent];
168   }
170   if ([parent isKindOfClass:[mozMenuItemAccessible class]] &&
171       [parent geckoAccessible]->Role() == roles::PARENT_MENUITEM) {
172     // We are a submenu. If our parent menu item is in an open menu
173     // we should not be ignored
174     id grandparent = [parent moxParent];
175     if ([grandparent isKindOfClass:[mozMenuAccessible class]]) {
176       mozMenuAccessible* parentMenu =
177           static_cast<mozMenuAccessible*>(grandparent);
178       return ![parentMenu isOpened];
179     }
180   }
182   // Otherwise, we call into our superclass's ignore method
183   // to handle menus that are not submenus
184   return [super moxIgnoreWithParent:parent];
187 - (NSArray*)moxVisibleChildren {
188   // VO expects us to expose two lists of children on menus: all children
189   // (done in moxUnignoredChildren), and children which are visible (here).
190   // We implement ignoreWithParent for both menus and menu items
191   // to ensure moxUnignoredChildren returns a complete list of children
192   // regardless of visibility, see comments in those methods for additional
193   // info.
194   return [[self moxChildren]
195       filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
196                                                    mozAccessible* child,
197                                                    NSDictionary* bindings) {
198         if (LocalAccessible* acc = [child geckoAccessible]->AsLocal()) {
199           if (acc->IsContent() && acc->GetContent()->IsXULElement()) {
200             return ((acc->VisibilityState() & states::INVISIBLE) == 0);
201           }
202         }
203         return true;
204       }]];
207 - (id)moxTitleUIElement {
208   id parent = [self moxUnignoredParent];
209   if (parent && [parent isKindOfClass:[mozAccessible class]]) {
210     return parent;
211   }
213   return nil;
216 - (void)moxPostNotification:(NSString*)notification {
217   if ([notification isEqualToString:@"AXMenuOpened"]) {
218     mIsOpened = YES;
219   } else if ([notification isEqualToString:@"AXMenuClosed"]) {
220     mIsOpened = NO;
221   }
223   [super moxPostNotification:notification];
226 - (void)expire {
227   if (mIsOpened) {
228     // VO needs to receive a menu closed event when the menu goes away.
229     // If the menu is being destroyed, send a menu closed event first.
230     [self moxPostNotification:@"AXMenuClosed"];
231   }
233   [super expire];
236 - (BOOL)isOpened {
237   return mIsOpened;
240 @end
242 @implementation mozMenuItemAccessible
244 - (NSString*)moxLabel {
245   return @"";
248 - (BOOL)moxIgnoreWithParent:(mozAccessible*)parent {
249   // This helps us generate the correct moxChildren array for
250   // a mozMenuAccessible; the returned array should contain all
251   // menu items, regardless of if they are visible or not.
252   // Because moxChildren does ignore filtering, and because
253   // our base ignore method filters out invisible accessibles,
254   // we override this method.
255   Accessible* parentAcc = [parent geckoAccessible];
256   if (parentAcc) {
257     Accessible* grandparentAcc = parentAcc->Parent();
258     if (mozAccessible* directGrandparent =
259             GetNativeFromGeckoAccessible(grandparentAcc)) {
260       if ([directGrandparent isKindOfClass:[MOXWebAreaAccessible class]]) {
261         return [parent moxIgnoreWithParent:directGrandparent];
262       }
263     }
264   }
266   id grandparent = [parent moxParent];
267   if ([grandparent isKindOfClass:[mozMenuItemAccessible class]]) {
268     mozMenuItemAccessible* acc =
269         static_cast<mozMenuItemAccessible*>(grandparent);
270     if ([acc geckoAccessible]->Role() == roles::PARENT_MENUITEM) {
271       mozMenuAccessible* parentMenu = static_cast<mozMenuAccessible*>(parent);
272       // if we are a menu item in a submenu, display only when
273       // parent menu item is open
274       return ![parentMenu isOpened];
275     }
276   }
278   // Otherwise, we call into our superclass's method to handle
279   // menuitems that are not within submenus
280   return [super moxIgnoreWithParent:parent];
283 - (NSString*)moxMenuItemMarkChar {
284   LocalAccessible* acc = mGeckoAccessible->AsLocal();
285   if (acc && acc->IsContent() &&
286       acc->GetContent()->IsXULElement(nsGkAtoms::menuitem)) {
287     // We need to provide a marker character. This is the visible "√" you see
288     // on dropdown menus. In our a11y tree this is a single child text node
289     // of the menu item.
290     // We do this only with XUL menuitems that conform to the native theme, and
291     // not with aria menu items that might have a pseudo element or something.
292     if (acc->ChildCount() == 1 &&
293         acc->LocalFirstChild()->Role() == roles::STATICTEXT) {
294       nsAutoString marker;
295       acc->LocalFirstChild()->Name(marker);
296       if (marker.Length() == 1) {
297         return nsCocoaUtils::ToNSString(marker);
298       }
299     }
300   }
302   return nil;
305 - (NSNumber*)moxSelected {
306   // Our focused state is equivelent to native selected states for menus.
307   return @([self stateWithMask:states::FOCUSED] != 0);
310 - (void)handleAccessibleEvent:(uint32_t)eventType {
311   switch (eventType) {
312     case nsIAccessibleEvent::EVENT_FOCUS:
313       // Our focused state is equivelent to native selected states for menus.
314       mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
315       [parent moxPostNotification:
316                   NSAccessibilitySelectedChildrenChangedNotification];
317       break;
318   }
320   [super handleAccessibleEvent:eventType];
323 - (void)moxPerformPress {
324   [super moxPerformPress];
325   // when a menu item is pressed (chosen), we need to tell
326   // VoiceOver about it, so we send this notification
327   [self moxPostNotification:@"AXMenuItemSelected"];
330 @end