зеркало из https://github.com/mozilla/gecko-dev.git
276 строки
7.0 KiB
Plaintext
276 строки
7.0 KiB
Plaintext
/* clang-format off */
|
|
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* clang-format on */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#import "MOXWebAreaAccessible.h"
|
|
|
|
#import "MOXSearchInfo.h"
|
|
|
|
#include "nsCocoaUtils.h"
|
|
#include "DocAccessibleParent.h"
|
|
|
|
using namespace mozilla::a11y;
|
|
|
|
@implementation MOXRootGroup
|
|
|
|
- (id)initWithParent:(MOXWebAreaAccessible*)parent {
|
|
// The parent is always a MOXWebAreaAccessible
|
|
mParent = parent;
|
|
return [super init];
|
|
}
|
|
|
|
- (NSString*)moxRole {
|
|
return NSAccessibilityGroupRole;
|
|
}
|
|
|
|
- (NSString*)moxRoleDescription {
|
|
if ([[self moxSubrole] isEqualToString:@"AXLandmarkApplication"]) {
|
|
return utils::LocalizedString(u"application"_ns);
|
|
}
|
|
|
|
return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
|
|
}
|
|
|
|
- (id<mozAccessible>)moxParent {
|
|
return mParent;
|
|
}
|
|
|
|
- (NSArray*)moxChildren {
|
|
// Reparent the children of the web area here.
|
|
return [mParent rootGroupChildren];
|
|
}
|
|
|
|
- (NSString*)moxIdentifier {
|
|
// This is mostly for testing purposes to assert that this is the generated
|
|
// root group.
|
|
return @"root-group";
|
|
}
|
|
|
|
- (NSString*)moxSubrole {
|
|
// Steal the subrole internally mapped to the web area.
|
|
return [mParent moxSubrole];
|
|
}
|
|
|
|
- (id)moxHitTest:(NSPoint)point {
|
|
return [mParent moxHitTest:point];
|
|
}
|
|
|
|
- (NSValue*)moxPosition {
|
|
return [mParent moxPosition];
|
|
}
|
|
|
|
- (NSValue*)moxSize {
|
|
return [mParent moxSize];
|
|
}
|
|
|
|
- (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate {
|
|
MOXSearchInfo* search =
|
|
[[MOXSearchInfo alloc] initWithParameters:searchPredicate andRoot:self];
|
|
|
|
return [search performSearch];
|
|
}
|
|
|
|
- (NSNumber*)moxUIElementCountForSearchPredicate:
|
|
(NSDictionary*)searchPredicate {
|
|
return [NSNumber
|
|
numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
|
|
count]];
|
|
}
|
|
|
|
- (BOOL)disableChild:(id)child {
|
|
return NO;
|
|
}
|
|
|
|
- (void)expire {
|
|
mParent = nil;
|
|
[super expire];
|
|
}
|
|
|
|
- (BOOL)isExpired {
|
|
MOZ_ASSERT((mParent == nil) == mIsExpired);
|
|
|
|
return [super isExpired];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MOXWebAreaAccessible
|
|
|
|
- (NSString*)moxRole {
|
|
// The OS role is AXWebArea regardless of the gecko role
|
|
// (APPLICATION or DOCUMENT).
|
|
// If the web area has a role of APPLICATION, its root group will
|
|
// reflect that in a subrole/description.
|
|
return @"AXWebArea";
|
|
}
|
|
|
|
- (NSString*)moxRoleDescription {
|
|
// The role description is "HTML Content" regardless of the gecko role
|
|
// (APPLICATION or DOCUMENT)
|
|
return utils::LocalizedString(u"htmlContent"_ns);
|
|
}
|
|
|
|
- (NSURL*)moxURL {
|
|
if ([self isExpired]) {
|
|
return nil;
|
|
}
|
|
|
|
nsAutoString url;
|
|
if (mGeckoAccessible.IsAccessible()) {
|
|
MOZ_ASSERT(mGeckoAccessible.AsAccessible()->IsDoc());
|
|
DocAccessible* acc = mGeckoAccessible.AsAccessible()->AsDoc();
|
|
acc->URL(url);
|
|
} else {
|
|
RemoteAccessible* proxy = mGeckoAccessible.AsProxy();
|
|
proxy->URL(url);
|
|
}
|
|
|
|
if (url.IsEmpty()) {
|
|
return nil;
|
|
}
|
|
|
|
return [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
|
|
}
|
|
|
|
- (NSNumber*)moxLoaded {
|
|
if ([self isExpired]) {
|
|
return nil;
|
|
}
|
|
// We are loaded if we aren't busy or stale
|
|
return @([self stateWithMask:(states::BUSY & states::STALE)] == 0);
|
|
}
|
|
|
|
// overrides
|
|
- (NSNumber*)moxLoadingProgress {
|
|
if ([self isExpired]) {
|
|
return nil;
|
|
}
|
|
|
|
if ([self stateWithMask:states::STALE] != 0) {
|
|
// We expose stale state until the document is ready (DOM is loaded and tree
|
|
// is constructed) so we indicate load hasn't started while this state is
|
|
// present.
|
|
return @0.0;
|
|
}
|
|
|
|
if ([self stateWithMask:states::BUSY] != 0) {
|
|
// We expose state busy until the document and all its subdocuments are
|
|
// completely loaded, so we indicate partial loading here
|
|
return @0.5;
|
|
}
|
|
|
|
// if we are not busy and not stale, we are loaded
|
|
return @1.0;
|
|
}
|
|
|
|
- (NSArray*)moxLinkUIElements {
|
|
NSDictionary* searchPredicate = @{
|
|
@"AXSearchKey" : @"AXLinkSearchKey",
|
|
@"AXImmediateDescendantsOnly" : @NO,
|
|
@"AXResultsLimit" : @(-1),
|
|
@"AXDirection" : @"AXDirectionNext",
|
|
};
|
|
|
|
return [self moxUIElementsForSearchPredicate:searchPredicate];
|
|
}
|
|
|
|
- (void)handleAccessibleEvent:(uint32_t)eventType {
|
|
switch (eventType) {
|
|
case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
|
|
[self moxPostNotification:
|
|
NSAccessibilityFocusedUIElementChangedNotification];
|
|
if ((mGeckoAccessible.IsProxy() && mGeckoAccessible.AsProxy()->IsDoc() &&
|
|
mGeckoAccessible.AsProxy()->AsDoc()->IsTopLevel()) ||
|
|
(mGeckoAccessible.IsAccessible() &&
|
|
!mGeckoAccessible.AsAccessible()->IsRoot() &&
|
|
mGeckoAccessible.AsAccessible()
|
|
->AsDoc()
|
|
->ParentDocument()
|
|
->IsRoot())) {
|
|
// we fire an AXLoadComplete event on top-level documents only
|
|
[self moxPostNotification:@"AXLoadComplete"];
|
|
} else {
|
|
// otherwise the doc belongs to an iframe (IsTopLevelInContentProcess)
|
|
// and we fire AXLayoutComplete instead
|
|
[self moxPostNotification:@"AXLayoutComplete"];
|
|
}
|
|
break;
|
|
}
|
|
|
|
[super handleAccessibleEvent:eventType];
|
|
}
|
|
|
|
- (NSArray*)rootGroupChildren {
|
|
// This method is meant to expose the doc's children to the root group.
|
|
return [super moxChildren];
|
|
}
|
|
|
|
- (NSArray*)moxUnignoredChildren {
|
|
if (id rootGroup = [self rootGroup]) {
|
|
return @[ [self rootGroup] ];
|
|
}
|
|
|
|
// There is no root group, expose the children here directly.
|
|
return [super moxUnignoredChildren];
|
|
}
|
|
|
|
- (BOOL)moxBlockSelector:(SEL)selector {
|
|
if (selector == @selector(moxSubrole)) {
|
|
// Never expose a subrole for a web area.
|
|
return YES;
|
|
}
|
|
|
|
if (selector == @selector(moxElementBusy)) {
|
|
// Don't confuse aria-busy with a document's busy state.
|
|
return YES;
|
|
}
|
|
|
|
return [super moxBlockSelector:selector];
|
|
}
|
|
|
|
- (void)moxPostNotification:(NSString*)notification {
|
|
if (![notification isEqualToString:@"AXElementBusyChanged"]) {
|
|
// Suppress AXElementBusyChanged since it uses gecko's BUSY state
|
|
// to tell VoiceOver about aria-busy changes. We use that state
|
|
// differently in documents.
|
|
[super moxPostNotification:notification];
|
|
}
|
|
}
|
|
|
|
- (id)rootGroup {
|
|
NSArray* children = [super moxUnignoredChildren];
|
|
if (mRole != roles::APPLICATION && [children count] == 1 &&
|
|
[[[children firstObject] moxUnignoredChildren] count] != 0) {
|
|
// We only need a root group if our document:
|
|
// (1) has multiple children, or
|
|
// (2) a one child that is a leaf, or
|
|
// (3) has a role of APPLICATION
|
|
return nil;
|
|
}
|
|
|
|
if (!mRootGroup) {
|
|
mRootGroup = [[MOXRootGroup alloc] initWithParent:self];
|
|
}
|
|
|
|
return mRootGroup;
|
|
}
|
|
|
|
- (void)expire {
|
|
[mRootGroup expire];
|
|
[super expire];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
// This object can only be dealoced after the gecko accessible wrapper
|
|
// reference is released, and that happens after expire is called.
|
|
MOZ_ASSERT([self isExpired]);
|
|
[mRootGroup release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
@end
|