| // 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 |