no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / accessible / mac / MOXWebAreaAccessible.mm
blobc1ae585fa1e63b58f4bf4e4aec2eb3646964a71e
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 "MOXWebAreaAccessible.h"
10 #import "MOXSearchInfo.h"
11 #import "MacUtils.h"
13 #include "nsAccUtils.h"
14 #include "nsCocoaUtils.h"
15 #include "DocAccessible.h"
16 #include "DocAccessibleParent.h"
18 using namespace mozilla::a11y;
20 @implementation MOXRootGroup
22 - (id)initWithParent:(MOXWebAreaAccessible*)parent {
23   // The parent is always a MOXWebAreaAccessible
24   mParent = parent;
25   return [super init];
28 - (NSString*)moxRole {
29   return NSAccessibilityGroupRole;
32 - (NSString*)moxRoleDescription {
33   if ([[self moxSubrole] isEqualToString:@"AXLandmarkApplication"]) {
34     return utils::LocalizedString(u"application"_ns);
35   }
37   return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
40 - (id<mozAccessible>)moxParent {
41   return mParent;
44 - (NSArray*)moxChildren {
45   // Reparent the children of the web area here.
46   return [mParent rootGroupChildren];
49 - (NSString*)moxIdentifier {
50   // This is mostly for testing purposes to assert that this is the generated
51   // root group.
52   return @"root-group";
55 - (NSString*)moxSubrole {
56   // Steal the subrole internally mapped to the web area.
57   return [mParent moxSubrole];
60 - (id)moxHitTest:(NSPoint)point {
61   return [mParent moxHitTest:point];
64 - (NSValue*)moxPosition {
65   return [mParent moxPosition];
68 - (NSValue*)moxSize {
69   return [mParent moxSize];
72 - (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate {
73   MOXSearchInfo* search =
74       [[[MOXSearchInfo alloc] initWithParameters:searchPredicate
75                                          andRoot:self] autorelease];
77   return [search performSearch];
80 - (NSNumber*)moxUIElementCountForSearchPredicate:
81     (NSDictionary*)searchPredicate {
82   return [NSNumber
83       numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
84                            count]];
87 - (BOOL)disableChild:(id)child {
88   return NO;
91 - (void)expire {
92   mParent = nil;
93   [super expire];
96 - (BOOL)isExpired {
97   MOZ_ASSERT((mParent == nil) == mIsExpired);
99   return [super isExpired];
102 @end
104 @implementation MOXWebAreaAccessible
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";
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);
120 - (NSURL*)moxURL {
121   if ([self isExpired]) {
122     return nil;
123   }
125   nsAutoString url;
126   MOZ_ASSERT(mGeckoAccessible->IsDoc());
127   nsAccUtils::DocumentURL(mGeckoAccessible, url);
129   if (url.IsEmpty()) {
130     return nil;
131   }
133   return [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
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);
144 // overrides
145 - (NSNumber*)moxLoadingProgress {
146   if ([self isExpired]) {
147     return nil;
148   }
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   }
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   }
163   // if we are not busy and not stale, we are loaded
164   return @1.0;
167 - (NSArray*)moxLinkUIElements {
168   NSDictionary* searchPredicate = @{
169     @"AXSearchKey" : @"AXLinkSearchKey",
170     @"AXImmediateDescendantsOnly" : @NO,
171     @"AXResultsLimit" : @(-1),
172     @"AXDirection" : @"AXDirectionNext",
173   };
175   return [self moxUIElementsForSearchPredicate:searchPredicate];
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   }
204   [super handleAccessibleEvent:eventType];
207 - (NSArray*)rootGroupChildren {
208   // This method is meant to expose the doc's children to the root group.
209   return [super moxChildren];
212 - (NSArray*)moxUnignoredChildren {
213   if (id rootGroup = [self rootGroup]) {
214     return @[ [self rootGroup] ];
215   }
217   // There is no root group, expose the children here directly.
218   return [super moxUnignoredChildren];
221 - (BOOL)moxBlockSelector:(SEL)selector {
222   if (selector == @selector(moxSubrole)) {
223     // Never expose a subrole for a web area.
224     return YES;
225   }
227   if (selector == @selector(moxElementBusy)) {
228     // Don't confuse aria-busy with a document's busy state.
229     return YES;
230   }
232   return [super moxBlockSelector:selector];
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   }
244 - (id)rootGroup {
245   NSArray* children = [super moxUnignoredChildren];
246   if (mRole == roles::DOCUMENT && [children count] == 1 &&
247       [[[children firstObject] moxUnignoredChildren] count] != 0) {
248     // We only need a root group if our document:
249     // (1) has multiple children, or
250     // (2) a one child that is a leaf, or
251     // (3) has a role other than the default document role
252     return nil;
253   }
255   if (!mRootGroup) {
256     mRootGroup = [[MOXRootGroup alloc] initWithParent:self];
257   }
259   return mRootGroup;
262 - (void)expire {
263   [mRootGroup expire];
264   [super expire];
267 - (void)dealloc {
268   // This object can only be dealoced after the gecko accessible wrapper
269   // reference is released, and that happens after expire is called.
270   MOZ_ASSERT([self isExpired]);
271   [mRootGroup release];
273   [super dealloc];
276 @end