1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ui/accessibility/platform/ax_platform_node_mac.h"
#include "base/apple/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/accessibility/platform/ax_platform_node_cocoa.h"
namespace {
using RoleMap = std::map<ax::mojom::Role, NSString*>;
using EventMap = std::map<ax::mojom::Event, NSString*>;
using ActionList = std::vector<std::pair<ax::mojom::Action, NSString*>>;
void PostAnnouncementNotification(NSString* announcement,
NSWindow* window,
bool is_polite) {
NSAccessibilityPriorityLevel priority =
is_polite ? NSAccessibilityPriorityMedium : NSAccessibilityPriorityHigh;
NSDictionary* notification_info = @{
NSAccessibilityAnnouncementKey : announcement,
NSAccessibilityPriorityKey : @(priority)
};
// On Mojave, announcements from an inactive window aren't spoken.
NSAccessibilityPostNotificationWithUserInfo(
window, NSAccessibilityAnnouncementRequestedNotification,
notification_info);
}
void NotifyMacEvent(AXPlatformNodeCocoa* target, ax::mojom::Event event_type) {
if (![target AXWindow]) {
// A child tree is not attached to the window. Return early, otherwise
// AppKit will hang trying to reach the root, resulting in a bug where
// VoiceOver keeps repeating "[appname] is not responding".
return;
}
NSString* notification =
[AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type];
if (notification)
NSAccessibilityPostNotification(target, notification);
}
} // namespace
namespace ui {
// static
AXPlatformNode::Pointer AXPlatformNode::Create(
AXPlatformNodeDelegate& delegate) {
AXPlatformNode* node = new AXPlatformNodeMac();
node->Init(delegate);
return Pointer(node);
}
// static
AXPlatformNode* AXPlatformNode::FromNativeViewAccessible(
gfx::NativeViewAccessible accessible) {
AXPlatformNodeCocoa* node_cocoa =
base::apple::ObjCCast<AXPlatformNodeCocoa>(accessible.Get());
if (node_cocoa) {
return [node_cocoa node];
}
return nullptr;
}
struct AXPlatformNodeMac::ObjCStorage {
AXPlatformNodeCocoa* __strong native_node;
};
AXPlatformNodeMac::AXPlatformNodeMac()
: objc_storage_(std::make_unique<ObjCStorage>()) {}
AXPlatformNodeMac::~AXPlatformNodeMac() = default;
void AXPlatformNodeMac::Destroy() {
if (objc_storage_->native_node) {
[objc_storage_->native_node detachAndNotifyDestroyed:YES];
// Also, clear the pointer to make accidental use-after-free impossible.
objc_storage_->native_node = nil;
}
AXPlatformNodeBase::Destroy();
}
// On Mac, the checked state is mapped to AXValue.
bool AXPlatformNodeMac::IsPlatformCheckable() const {
if (GetRole() == ax::mojom::Role::kTab) {
// On Mac, tabs are exposed as radio buttons, and are treated as checkable.
// Also, the internal State::kSelected is be mapped to checked via AXValue.
return true;
}
return AXPlatformNodeBase::IsPlatformCheckable();
}
AXPlatformNodeCocoa* AXPlatformNodeMac::GetNativeWrapper() const {
return objc_storage_->native_node;
}
AXPlatformNodeCocoa* AXPlatformNodeMac::ReleaseNativeWrapper() {
AXPlatformNodeCocoa* native_node = objc_storage_->native_node;
objc_storage_->native_node = nil;
return native_node;
}
void AXPlatformNodeMac::SetNativeWrapper(AXPlatformNodeCocoa* native_node) {
objc_storage_->native_node = native_node;
}
gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() {
if (!objc_storage_->native_node) {
objc_storage_->native_node =
[[AXPlatformNodeCocoa alloc] initWithNode:this];
}
return gfx::NativeViewAccessible(objc_storage_->native_node);
}
void AXPlatformNodeMac::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
AXPlatformNodeBase::NotifyAccessibilityEvent(event_type);
GetNativeViewAccessible();
// Handle special cases.
// Alerts and live regions go through the announcement API instead of the
// regular NSAccessibility notification system.
if (event_type == ax::mojom::Event::kAlert ||
event_type == ax::mojom::Event::kLiveRegionChanged) {
if (AXAnnouncementSpec* announcement =
[objc_storage_->native_node announcementForEvent:event_type]) {
[objc_storage_->native_node scheduleLiveRegionAnnouncement:announcement];
}
return;
}
if (event_type == ax::mojom::Event::kSelection) {
ax::mojom::Role role = GetRole();
if (IsMenuItem(role)) {
// On Mac, map menu item selection to a focus event.
NotifyMacEvent(objc_storage_->native_node, ax::mojom::Event::kFocus);
return;
} else if (IsListItem(role)) {
if (const AXPlatformNodeBase* container = GetSelectionContainer()) {
if (container->GetRole() == ax::mojom::Role::kListBox &&
!container->HasState(ax::mojom::State::kMultiselectable) &&
GetDelegate()->GetFocus() == GetNativeViewAccessible()) {
NotifyMacEvent(objc_storage_->native_node, ax::mojom::Event::kFocus);
return;
}
}
}
}
// Otherwise, use mappings between ax::mojom::Event and NSAccessibility
// notifications from the EventMap above.
NotifyMacEvent(objc_storage_->native_node, event_type);
}
void AXPlatformNodeMac::AnnounceTextAs(const std::u16string& text,
AnnouncementType announcement_type) {
PostAnnouncementNotification(base::SysUTF16ToNSString(text),
[objc_storage_->native_node AXWindow],
announcement_type == AnnouncementType::kPolite);
}
bool IsNameExposedInAXValueForRole(ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kListMarker:
case ax::mojom::Role::kMenuListOption:
case ax::mojom::Role::kStaticText:
case ax::mojom::Role::kTitleBar:
return true;
default:
return false;
}
}
void AXPlatformNodeMac::AddAttributeToList(const char* name,
const char* value,
PlatformAttributeList* attributes) {
NOTREACHED();
}
} // namespace ui
|