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 "MOXWebAreaAccessible.h"
10 #import "MOXSearchInfo.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
28 - (NSString*)moxRole {
29 return NSAccessibilityGroupRole;
32 - (NSString*)moxRoleDescription {
33 if ([[self moxSubrole] isEqualToString:@"AXLandmarkApplication"]) {
34 return utils::LocalizedString(u"application"_ns);
37 return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
40 - (id<mozAccessible>)moxParent {
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
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];
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 {
83 numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
87 - (BOOL)disableChild:(id)child {
97 MOZ_ASSERT((mParent == nil) == mIsExpired);
99 return [super isExpired];
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.
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);
121 if ([self isExpired]) {
126 MOZ_ASSERT(mGeckoAccessible->IsDoc());
127 nsAccUtils::DocumentURL(mGeckoAccessible, url);
133 return [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
136 - (NSNumber*)moxLoaded {
137 if ([self isExpired]) {
140 // We are loaded if we aren't busy or stale
141 return @([self stateWithMask:(states::BUSY & states::STALE)] == 0);
145 - (NSNumber*)moxLoadingProgress {
146 if ([self isExpired]) {
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
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
163 // if we are not busy and not stale, we are loaded
167 - (NSArray*)moxLinkUIElements {
168 NSDictionary* searchPredicate = @{
169 @"AXSearchKey" : @"AXLinkSearchKey",
170 @"AXImmediateDescendantsOnly" : @NO,
171 @"AXResultsLimit" : @(-1),
172 @"AXDirection" : @"AXDirectionNext",
175 return [self moxUIElementsForSearchPredicate:searchPredicate];
178 - (void)handleAccessibleEvent:(uint32_t)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"];
197 // otherwise the doc belongs to an iframe (IsTopLevelInContentProcess)
198 // and we fire AXLayoutComplete instead
199 [self moxPostNotification:@"AXLayoutComplete"];
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] ];
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.
227 if (selector == @selector(moxElementBusy)) {
228 // Don't confuse aria-busy with a document's busy state.
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];
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
256 mRootGroup = [[MOXRootGroup alloc] initWithParent:self];
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];