blob: 2fbb8da1c29ad600252ae57b207db8420a948316 [file] [log] [blame]
// 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) {
if (role_string == "abbr")
return NSAccessibilityGroupRole;
if (role_string == "alertDialog")
return NSAccessibilityGroupRole;
if (role_string == "alert")
return NSAccessibilityGroupRole;
if (role_string == "annotation")
return NSAccessibilityUnknownRole;
if (role_string == "application")
return NSAccessibilityGroupRole;
if (role_string == "article")
return NSAccessibilityGroupRole;
if (role_string == "audio")
return NSAccessibilityGroupRole;
if (role_string == "banner")
return NSAccessibilityGroupRole;
if (role_string == "blockquote")
return NSAccessibilityGroupRole;
if (role_string == "busyIndicator")
return NSAccessibilityBusyIndicatorRole;
if (role_string == "button")
return NSAccessibilityButtonRole;
if (role_string == "buttonDropDown")
return NSAccessibilityButtonRole;
if (role_string == "canvas")
return NSAccessibilityImageRole;
if (role_string == "caption")
return NSAccessibilityGroupRole;
if (role_string == "checkBox")
return NSAccessibilityCheckBoxRole;
if (role_string == "colorWell")
return NSAccessibilityColorWellRole;
if (role_string == "column")
return NSAccessibilityColumnRole;
if (role_string == "comboBox")
return NSAccessibilityComboBoxRole;
if (role_string == "complementary")
return NSAccessibilityGroupRole;
if (role_string == "contentInfo")
return NSAccessibilityGroupRole;
if (role_string == "definition")
return NSAccessibilityGroupRole;
if (role_string == "descriptionListDetail")
return NSAccessibilityGroupRole;
if (role_string == "descriptionList")
return NSAccessibilityListRole;
if (role_string == "descriptionListTerm")
return NSAccessibilityGroupRole;
if (role_string == "details")
return NSAccessibilityGroupRole;
if (role_string == "dialog")
return NSAccessibilityGroupRole;
if (role_string == "directory")
return NSAccessibilityListRole;
if (role_string == "disclosureTriangle")
return NSAccessibilityDisclosureTriangleRole;
if (role_string == "div")
return NSAccessibilityGroupRole;
if (role_string == "document")
return NSAccessibilityGroupRole;
if (role_string == "embeddedObject")
return NSAccessibilityGroupRole;
if (role_string == "figcaption")
return NSAccessibilityGroupRole;
if (role_string == "figure")
return NSAccessibilityGroupRole;
if (role_string == "footer")
return NSAccessibilityGroupRole;
if (role_string == "form")
return NSAccessibilityGroupRole;
if (role_string == "genericContainer")
return NSAccessibilityGroupRole;
if (role_string == "grid")
return NSAccessibilityGroupRole;
if (role_string == "group")
return NSAccessibilityGroupRole;
if (role_string == "iframe")
return NSAccessibilityGroupRole;
if (role_string == "iframePresentational")
return NSAccessibilityGroupRole;
if (role_string == "ignored")
return NSAccessibilityUnknownRole;
if (role_string == "imageMapLink")
return NSAccessibilityLinkRole;
if (role_string == "imageMap")
return NSAccessibilityGroupRole;
if (role_string == "image")
return NSAccessibilityImageRole;
if (role_string == "labelText")
return NSAccessibilityGroupRole;
if (role_string == "legend")
return NSAccessibilityGroupRole;
if (role_string == "link")
return NSAccessibilityLinkRole;
if (role_string == "listBoxOption")
return NSAccessibilityStaticTextRole;
if (role_string == "listBox")
return NSAccessibilityListRole;
if (role_string == "listItem")
return NSAccessibilityGroupRole;
if (role_string == "list")
return NSAccessibilityListRole;
if (role_string == "log")
return NSAccessibilityGroupRole;
if (role_string == "main")
return NSAccessibilityGroupRole;
if (role_string == "mark")
return NSAccessibilityGroupRole;
if (role_string == "marquee")
return NSAccessibilityGroupRole;
if (role_string == "math")
return NSAccessibilityGroupRole;
if (role_string == "menu")
return NSAccessibilityMenuRole;
if (role_string == "menuBar")
return NSAccessibilityMenuBarRole;
if (role_string == "menuButton")
return NSAccessibilityButtonRole;
if (role_string == "menuItem")
return NSAccessibilityMenuItemRole;
if (role_string == "menuItemCheckBox")
return NSAccessibilityMenuItemRole;
if (role_string == "menuItemRadio")
return NSAccessibilityMenuItemRole;
if (role_string == "menuListOption")
return NSAccessibilityMenuItemRole;
if (role_string == "menuListPopup")
return NSAccessibilityUnknownRole;
if (role_string == "meter")
return NSAccessibilityProgressIndicatorRole;
if (role_string == "navigation")
return NSAccessibilityGroupRole;
if (role_string == "note")
return NSAccessibilityGroupRole;
if (role_string == "outline")
return NSAccessibilityOutlineRole;
if (role_string == "paragraph")
return NSAccessibilityGroupRole;
if (role_string == "popUpButton")
return NSAccessibilityPopUpButtonRole;
if (role_string == "pre")
return NSAccessibilityGroupRole;
if (role_string == "presentational")
return NSAccessibilityGroupRole;
if (role_string == "progressIndicator")
return NSAccessibilityProgressIndicatorRole;
if (role_string == "radioButton")
return NSAccessibilityRadioButtonRole;
if (role_string == "radioGroup")
return NSAccessibilityRadioGroupRole;
if (role_string == "region")
return NSAccessibilityGroupRole;
if (role_string == "row")
return NSAccessibilityRowRole;
if (role_string == "ruler")
return NSAccessibilityRulerRole;
if (role_string == "scrollBar")
return NSAccessibilityScrollBarRole;
if (role_string == "search")
return NSAccessibilityGroupRole;
if (role_string == "searchBox")
return NSAccessibilityTextFieldRole;
if (role_string == "slider")
return NSAccessibilitySliderRole;
if (role_string == "sliderThumb")
return NSAccessibilityValueIndicatorRole;
if (role_string == "spinButton")
return NSAccessibilityIncrementorRole;
if (role_string == "splitter")
return NSAccessibilitySplitterRole;
if (role_string == "staticText")
return NSAccessibilityStaticTextRole;
if (role_string == "status")
return NSAccessibilityGroupRole;
if (role_string == "svgRoot")
return NSAccessibilityGroupRole;
if (role_string == "switch")
return NSAccessibilityCheckBoxRole;
if (role_string == "tabGroup")
return NSAccessibilityTabGroupRole;
if (role_string == "tabList")
return NSAccessibilityTabGroupRole;
if (role_string == "tabPanel")
return NSAccessibilityGroupRole;
if (role_string == "tab")
return NSAccessibilityRadioButtonRole;
if (role_string == "tableHeaderContainer")
return NSAccessibilityGroupRole;
if (role_string == "table")
return NSAccessibilityTableRole;
if (role_string == "textField")
return NSAccessibilityTextFieldRole;
if (role_string == "time")
return NSAccessibilityGroupRole;
if (role_string == "timer")
return NSAccessibilityGroupRole;
if (role_string == "toggleButton")
return NSAccessibilityCheckBoxRole;
if (role_string == "toolbar")
return NSAccessibilityToolbarRole;
if (role_string == "treeGrid")
return NSAccessibilityTableRole;
if (role_string == "treeItem")
return NSAccessibilityRowRole;
if (role_string == "tree")
return NSAccessibilityOutlineRole;
if (role_string == "unknown")
return NSAccessibilityUnknownRole;
if (role_string == "tooltip")
return NSAccessibilityGroupRole;
if (role_string == "video")
return NSAccessibilityGroupRole;
if (role_string == "window")
return NSAccessibilityWindowRole;
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_);
if (!node_)
return nil;
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