cef/tests/cefclient/browser/osr_accessibility_node_mac.mm

613 lines
18 KiB
Plaintext
Raw Normal View History

// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.
// Sample implementation for the NSAccessibility protocol for interacting with
// VoiceOver and other accessibility clients.
#include "tests/cefclient/browser/osr_accessibility_node.h"
#import <AppKit/NSAccessibility.h>
#import <Cocoa/Cocoa.h>
#include "tests/cefclient/browser/osr_accessibility_helper.h"
namespace {
NSString* AxRoleToNSAxRole(const std::string& role_string) {
2023-01-02 23:59:03 +01:00
if (role_string == "abbr") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "alertDialog") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "alert") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "annotation") {
return NSAccessibilityUnknownRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "application") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "article") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "audio") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "banner") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "blockquote") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "busyIndicator") {
return NSAccessibilityBusyIndicatorRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "button") {
return NSAccessibilityButtonRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "buttonDropDown") {
return NSAccessibilityButtonRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "canvas") {
return NSAccessibilityImageRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "caption") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "checkBox") {
return NSAccessibilityCheckBoxRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "colorWell") {
return NSAccessibilityColorWellRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "column") {
return NSAccessibilityColumnRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "comboBox") {
return NSAccessibilityComboBoxRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "complementary") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "contentInfo") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "definition") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "descriptionListDetail") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "descriptionList") {
return NSAccessibilityListRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "descriptionListTerm") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "details") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "dialog") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "directory") {
return NSAccessibilityListRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "disclosureTriangle") {
return NSAccessibilityDisclosureTriangleRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "div") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "document") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "embeddedObject") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "figcaption") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "figure") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "footer") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "form") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "genericContainer") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "grid") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "group") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "iframe") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "iframePresentational") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "ignored") {
return NSAccessibilityUnknownRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "imageMapLink") {
return NSAccessibilityLinkRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "imageMap") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "image") {
return NSAccessibilityImageRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "labelText") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "legend") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "link") {
return NSAccessibilityLinkRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "listBoxOption") {
return NSAccessibilityStaticTextRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "listBox") {
return NSAccessibilityListRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "listItem") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "list") {
return NSAccessibilityListRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "log") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "main") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "mark") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "marquee") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "math") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menu") {
return NSAccessibilityMenuRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuBar") {
return NSAccessibilityMenuBarRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuButton") {
return NSAccessibilityButtonRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuItem") {
return NSAccessibilityMenuItemRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuItemCheckBox") {
return NSAccessibilityMenuItemRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuItemRadio") {
return NSAccessibilityMenuItemRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuListOption") {
return NSAccessibilityMenuItemRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "menuListPopup") {
return NSAccessibilityUnknownRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "meter") {
return NSAccessibilityProgressIndicatorRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "navigation") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "note") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "outline") {
return NSAccessibilityOutlineRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "paragraph") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "popUpButton") {
return NSAccessibilityPopUpButtonRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "pre") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "presentational") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "progressIndicator") {
return NSAccessibilityProgressIndicatorRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "radioButton") {
return NSAccessibilityRadioButtonRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "radioGroup") {
return NSAccessibilityRadioGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "region") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "row") {
return NSAccessibilityRowRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "ruler") {
return NSAccessibilityRulerRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "scrollBar") {
return NSAccessibilityScrollBarRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "search") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "searchBox") {
return NSAccessibilityTextFieldRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "slider") {
return NSAccessibilitySliderRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "sliderThumb") {
return NSAccessibilityValueIndicatorRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "spinButton") {
return NSAccessibilityIncrementorRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "splitter") {
return NSAccessibilitySplitterRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "staticText") {
return NSAccessibilityStaticTextRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "status") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "svgRoot") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "switch") {
return NSAccessibilityCheckBoxRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tabGroup") {
return NSAccessibilityTabGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tabList") {
return NSAccessibilityTabGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tabPanel") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tab") {
return NSAccessibilityRadioButtonRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tableHeaderContainer") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "table") {
return NSAccessibilityTableRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "textField") {
return NSAccessibilityTextFieldRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "time") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "timer") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "toggleButton") {
return NSAccessibilityCheckBoxRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "toolbar") {
return NSAccessibilityToolbarRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "treeGrid") {
return NSAccessibilityTableRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "treeItem") {
return NSAccessibilityRowRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tree") {
return NSAccessibilityOutlineRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "unknown") {
return NSAccessibilityUnknownRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "tooltip") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "video") {
return NSAccessibilityGroupRole;
2023-01-02 23:59:03 +01:00
}
if (role_string == "window") {
return NSAccessibilityWindowRole;
2023-01-02 23:59:03 +01:00
}
return [NSString stringWithUTF8String:role_string.c_str()];
}
inline int MiddleX(const CefRect& rect) {
return rect.x + rect.width / 2;
}
inline int MiddleY(const CefRect& rect) {
return rect.y + rect.height / 2;
}
} // namespace
// OsrAXNodeObject is sample implementation for the NSAccessibility protocol
// for interacting with VoiceOver and other accessibility clients.
@interface OsrAXNodeObject : NSObject {
// OsrAXNode* proxy object
client::OsrAXNode* node_;
CefNativeAccessible* parent_;
}
- (id)init:(client::OsrAXNode*)node;
+ (OsrAXNodeObject*)elementWithNode:(client::OsrAXNode*)node;
@end
@implementation OsrAXNodeObject
- (id)init:(client::OsrAXNode*)node {
node_ = node;
parent_ = node_->GetParentAccessibleObject();
if (!parent_) {
parent_ = node_->GetWindowHandle();
}
return self;
}
+ (OsrAXNodeObject*)elementWithNode:(client::OsrAXNode*)node {
// We manage the release ourself
return [[OsrAXNodeObject alloc] init:node];
}
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[OsrAXNodeObject self]]) {
OsrAXNodeObject* other = object;
return (node_ == other->node_);
} else {
return NO;
}
}
// Utility methods to map AX information received from renderer
// to platform properties
- (NSString*)axRole {
// Get the Role from CefAccessibilityHelper and Map to NSRole
return AxRoleToNSAxRole(node_->AxRole());
}
- (NSString*)axDescription {
std::string desc = node_->AxDescription();
return [NSString stringWithUTF8String:desc.c_str()];
}
- (NSString*)axName {
std::string desc = node_->AxName();
return [NSString stringWithUTF8String:desc.c_str()];
}
- (NSString*)axValue {
std::string desc = node_->AxValue();
return [NSString stringWithUTF8String:desc.c_str()];
}
- (void)doMouseClick:(cef_mouse_button_type_t)type {
CefRefPtr<CefBrowser> browser = node_->GetBrowser();
if (browser) {
CefMouseEvent mouse_event;
const CefRect& rect = node_->AxLocation();
mouse_event.x = MiddleX(rect);
mouse_event.y = MiddleY(rect);
mouse_event.modifiers = 0;
browser->GetHost()->SendMouseClickEvent(mouse_event, type, false, 1);
browser->GetHost()->SendMouseClickEvent(mouse_event, type, true, 1);
}
}
- (NSMutableArray*)getKids {
int numChilds = node_->GetChildCount();
if (numChilds > 0) {
NSMutableArray* kids = [NSMutableArray arrayWithCapacity:numChilds];
for (int index = 0; index < numChilds; index++) {
client::OsrAXNode* child = node_->ChildAtIndex(index);
[kids addObject:child ? CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
child->GetNativeAccessibleObject(node_))
: nil];
}
return kids;
}
return nil;
}
- (NSPoint)position {
CefRect cef_rect = node_->AxLocation();
NSPoint origin = NSMakePoint(cef_rect.x, cef_rect.y);
NSSize size = NSMakeSize(cef_rect.width, cef_rect.height);
NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(node_->GetWindowHandle());
origin.y = NSHeight([view bounds]) - origin.y;
NSPoint originInWindow = [view convertPoint:origin toView:nil];
NSRect point_rect = NSMakeRect(originInWindow.x, originInWindow.y, 0, 0);
NSPoint originInScreen =
[[view window] convertRectToScreen:point_rect].origin;
originInScreen.y = originInScreen.y - size.height;
return originInScreen;
}
- (NSSize)size {
CefRect cef_rect = node_->AxLocation();
NSRect rect =
NSMakeRect(cef_rect.x, cef_rect.y, cef_rect.width, cef_rect.height);
NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(node_->GetWindowHandle());
rect = [[view window] convertRectToScreen:rect];
return rect.size;
}
//
// accessibility protocol
//
// attributes
- (BOOL)accessibilityIsIgnored {
return NO;
}
- (NSArray*)accessibilityAttributeNames {
static NSArray* attributes = nil;
if (attributes == nil) {
attributes = [[NSArray alloc]
initWithObjects:NSAccessibilityRoleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityChildrenAttribute,
NSAccessibilityValueAttribute,
NSAccessibilityTitleAttribute,
NSAccessibilityDescriptionAttribute,
NSAccessibilityFocusedAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityWindowAttribute,
NSAccessibilityTopLevelUIElementAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute, nil];
}
return attributes;
}
- (id)accessibilityAttributeValue:(NSString*)attribute {
NSObject* typed_parent = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(parent_);
2023-01-02 23:59:03 +01:00
if (!node_) {
return nil;
2023-01-02 23:59:03 +01:00
}
if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
return [self axRole];
} else if ([attribute
isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
return NSAccessibilityRoleDescription([self axRole], nil);
} else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
// Just check if the app thinks we're focused.
id focusedElement = [NSApp
accessibilityAttributeValue:NSAccessibilityFocusedUIElementAttribute];
return [NSNumber numberWithBool:[focusedElement isEqual:self]];
} else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) {
return NSAccessibilityUnignoredAncestor(typed_parent);
} else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
return NSAccessibilityUnignoredChildren([self getKids]);
} else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) {
// We're in the same window as our parent.
return [typed_parent
accessibilityAttributeValue:NSAccessibilityWindowAttribute];
} else if ([attribute
isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) {
// We're in the same top level element as our parent.
return [typed_parent
accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
} else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) {
return [NSValue valueWithPoint:[self position]];
} else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) {
return [NSValue valueWithSize:[self size]];
} else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) {
return [self axDescription];
} else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
return [self axValue];
} else if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) {
return [self axName];
}
return nil;
}
- (id)accessibilityHitTest:(NSPoint)point {
return NSAccessibilityUnignoredAncestor(self);
}
- (NSArray*)accessibilityActionNames {
return [NSArray arrayWithObject:NSAccessibilityPressAction];
}
- (NSString*)accessibilityActionDescription:(NSString*)action {
return NSAccessibilityActionDescription(action);
}
- (void)accessibilityPerformAction:(NSString*)action {
if ([action isEqualToString:NSAccessibilityPressAction]) {
// Do Click on Default action
[self doMouseClick:MBT_LEFT];
} else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
// Right click for Context Menu
[self doMouseClick:MBT_RIGHT];
}
}
- (id)accessibilityFocusedUIElement {
return NSAccessibilityUnignoredAncestor(self);
}
- (BOOL)accessibilityNotifiesWhenDestroyed {
// Indicate that BrowserAccessibilityCocoa will post a notification when it's
// destroyed (see -detach). This allows VoiceOver to do some internal things
// more efficiently.
return YES;
}
@end
namespace client {
void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const {
NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(GetWindowHandle());
if (event_type == "focus") {
NSAccessibilityPostNotification(
view, NSAccessibilityFocusedUIElementChangedNotification);
} else if (event_type == "textChanged") {
NSAccessibilityPostNotification(view,
NSAccessibilityTitleChangedNotification);
} else if (event_type == "valueChanged") {
NSAccessibilityPostNotification(view,
NSAccessibilityValueChangedNotification);
} else if (event_type == "textSelectionChanged") {
NSAccessibilityPostNotification(view,
NSAccessibilityValueChangedNotification);
}
}
void OsrAXNode::Destroy() {
if (platform_accessibility_) {
NSAccessibilityPostNotification(
CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(platform_accessibility_),
NSAccessibilityUIElementDestroyedNotification);
}
delete this;
}
// Create and return NSAccessibility Implementation Object for Mac
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(
client::OsrAXNode* parent) {
if (!platform_accessibility_) {
platform_accessibility_ = CAST_NSOBJECT_TO_CEF_NATIVE_ACCESSIBLE(
[OsrAXNodeObject elementWithNode:this]);
SetParent(parent);
}
return platform_accessibility_;
}
} // namespace client