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 "mozSelectableElements.h"
9 #import "MOXWebAreaAccessible.h"
11 #include "LocalAccessible-inl.h"
12 #include "nsCocoaUtils.h"
14 using namespace mozilla::a11y;
16 @implementation mozSelectableAccessible
19 * Return the mozAccessibles that are selectable.
21 - (NSArray*)selectableChildren {
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];
29 toFilter = [self moxUnignoredChildren];
32 filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
34 NSDictionary* bindings) {
35 return [child isKindOfClass:[mozSelectableChildAccessible class]];
39 - (void)moxSetSelectedChildren:(NSArray*)selectedChildren {
40 for (id child in [self selectableChildren]) {
42 [selectedChildren indexOfObjectIdenticalTo:child] != NSNotFound;
43 [child moxSetSelected:@(selected)];
48 * Return the mozAccessibles that are actually selected.
50 - (NSArray*)moxSelectedChildren {
51 return [[self selectableChildren]
52 filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
54 NSDictionary* bindings) {
55 // Return mozSelectableChildAccessibles that have are selected (truthy
57 return [[(mozSelectableChildAccessible*)child moxSelected] boolValue];
63 @implementation mozSelectableChildAccessible
65 - (NSNumber*)moxSelected {
66 return @([self stateWithMask:states::SELECTED] != 0);
69 - (void)moxSetSelected:(NSNumber*)selected {
70 // Get SELECTABLE and UNAVAILABLE 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.
78 mGeckoAccessible->SetSelected([selected boolValue]);
83 @implementation mozTabGroupAccessible
86 return [self selectableChildren];
89 - (NSArray*)moxContents {
90 return [self moxUnignoredChildren];
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];
101 @implementation mozTabAccessible
103 - (NSString*)moxRoleDescription {
104 return utils::LocalizedString(u"tab"_ns);
108 // Retuens 1 if item is selected, 0 if not.
109 return [self moxSelected];
114 @implementation mozListboxAccessible
116 - (BOOL)moxIgnoreChild:(mozAccessible*)child {
117 if (!child || child->mRole == roles::GROUPING) {
121 return [super moxIgnoreChild:child];
124 - (BOOL)disableChild:(mozAccessible*)child {
125 return ![child isKindOfClass:[mozSelectableChildAccessible class]];
128 - (NSString*)moxOrientation {
129 return NSAccessibilityUnknownOrientationValue;
134 @implementation mozOptionAccessible
136 - (NSString*)moxTitle {
141 // Swap title and value of option so it behaves more like a AXStaticText.
142 return [super moxTitle];
147 @implementation mozMenuAccessible
149 - (NSString*)moxTitle {
153 - (NSString*)moxLabel {
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];
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];
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
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);
207 - (id)moxTitleUIElement {
208 id parent = [self moxUnignoredParent];
209 if (parent && [parent isKindOfClass:[mozAccessible class]]) {
216 - (void)moxPostNotification:(NSString*)notification {
217 if ([notification isEqualToString:@"AXMenuOpened"]) {
219 } else if ([notification isEqualToString:@"AXMenuClosed"]) {
223 [super moxPostNotification:notification];
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"];
242 @implementation mozMenuItemAccessible
244 - (NSString*)moxLabel {
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];
257 Accessible* grandparentAcc = parentAcc->Parent();
258 if (mozAccessible* directGrandparent =
259 GetNativeFromGeckoAccessible(grandparentAcc)) {
260 if ([directGrandparent isKindOfClass:[MOXWebAreaAccessible class]]) {
261 return [parent moxIgnoreWithParent:directGrandparent];
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];
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
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) {
295 acc->LocalFirstChild()->Name(marker);
296 if (marker.Length() == 1) {
297 return nsCocoaUtils::ToNSString(marker);
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 {
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];
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"];