Implement accessibility enhancements (issue #1217)

- Add new CefBrowserHost::SetAccessibilityState method for toggling
  accessibility state when readers are detected by the client.
- Add new CefAccessibilityHandler interface for the delivery of
  accessibility notifications to windowless (OSR) clients.
- Fix delivery of CefFocusHandler callbacks to windowless clients.
- cefclient: Add example windowless accessibility implementation on Windows and macOS.
- cefclient: Automatically detect screen readers on Windows and macOS.
This commit is contained in:
Nishant Kaushik
2017-05-12 18:28:25 +00:00
committed by Marshall Greenblatt
parent 64fcfa6068
commit 816f700d3e
51 changed files with 3476 additions and 8 deletions

View File

@@ -1279,6 +1279,10 @@ void BrowserWindowOsrGtk::OnImeCompositionRangeChanged(
CEF_REQUIRE_UI_THREAD();
}
void BrowserWindowOsrGtk::UpdateAccessibilityTree(CefRefPtr<CefValue> value) {
CEF_REQUIRE_UI_THREAD();
}
void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) {
REQUIRE_MAIN_THREAD();
DCHECK(!glarea_);

View File

@@ -80,6 +80,7 @@ class BrowserWindowOsrGtk : public BrowserWindow,
CefRefPtr<CefBrowser> browser,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
void UpdateAccessibilityTree(CefRefPtr<CefValue> value) OVERRIDE;
private:
~BrowserWindowOsrGtk();

View File

@@ -83,6 +83,8 @@ class BrowserWindowOsrMac : public BrowserWindow,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
void UpdateAccessibilityTree(CefRefPtr<CefValue> value) OVERRIDE;
private:
// Create the NSView.
void Create(ClientWindowHandle parent_handle, const CefRect& rect);

View File

@@ -13,10 +13,14 @@
#include "include/wrapper/cef_closure_task.h"
#include "tests/cefclient/browser/bytes_write_handler.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/text_input_client_osr_mac.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#import <AppKit/NSAccessibility.h>
namespace {
CefTextInputClientOSRMac* GetInputClientFromContext(
@@ -29,7 +33,7 @@ CefTextInputClientOSRMac* GetInputClientFromContext(
} // namespace
@interface BrowserOpenGLView
: NSOpenGLView <NSDraggingSource, NSDraggingDestination> {
: NSOpenGLView <NSDraggingSource, NSDraggingDestination, NSAccessibility> {
@private
NSTrackingArea* tracking_area_;
client::BrowserWindowOsrMac* browser_window_;
@@ -52,6 +56,9 @@ CefTextInputClientOSRMac* GetInputClientFromContext(
// For intreacting with IME.
NSTextInputContext* text_input_context_osr_mac_;
// Manages Accessibility Tree
client::OsrAccessibilityHelper* accessibility_helper_;
// Event monitor for scroll wheel end event.
id endWheelMonitor_;
}
@@ -99,6 +106,7 @@ CefTextInputClientOSRMac* GetInputClientFromContext(
- (NSRect)convertRectToBackingInternal:(NSRect)aRect;
- (void)ChangeCompositionRange:(CefRange)range
character_bounds:(const CefRenderHandler::RectList&) character_bounds;
- (void)UpdateAccessibilityTree:(CefRefPtr<CefValue>)value;
@end
@@ -948,6 +956,50 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
}
}
// NSAccessibility Protocol implementation.
- (BOOL)accessibilityIsIgnored {
if(!accessibility_helper_)
return YES;
else
return NO;
}
- (id)accessibilityAttributeValue:(NSString *)attribute {
if(!accessibility_helper_)
return [super accessibilityAttributeValue:attribute];
if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
return NSAccessibilityGroupRole;
} else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) {
client::OsrAXNode* node = accessibility_helper_->GetRootNode();
std::string desc = node ? node->AxDescription(): "";
return [NSString stringWithUTF8String:desc.c_str()];
} else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
client::OsrAXNode* node = accessibility_helper_->GetRootNode();
std::string desc = node ? node->AxValue(): "";
return [NSString stringWithUTF8String:desc.c_str()];
} else if ([attribute isEqualToString:
NSAccessibilityRoleDescriptionAttribute]) {
return NSAccessibilityRoleDescriptionForUIElement(self);
} else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
client::OsrAXNode* node = accessibility_helper_->GetRootNode();
// Add Root as first Kid
NSMutableArray* kids = [NSMutableArray arrayWithCapacity:1];
NSObject* child = node->GetNativeAccessibleObject(NULL);
[kids addObject: child];
return NSAccessibilityUnignoredChildren(kids);
} else {
return [super accessibilityAttributeValue:attribute];
}
}
- (id)accessibilityFocusedUIElement {
if (accessibility_helper_) {
client::OsrAXNode* node = accessibility_helper_->GetFocusedNode();
return node ? node->GetNativeAccessibleObject(NULL) : nil;
}
return nil;
}
// Utility methods.
- (void)resetDragDrop {
current_drag_op_ = NSDragOperationNone;
@@ -1142,6 +1194,21 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
if (client)
[client ChangeCompositionRange: range character_bounds:bounds];
}
- (void)UpdateAccessibilityTree:(CefRefPtr<CefValue>)value {
if (!accessibility_helper_) {
accessibility_helper_ = new client::OsrAccessibilityHelper(value,
[self getBrowser]);
} else {
accessibility_helper_->UpdateAccessibilityTree(value);
}
if (accessibility_helper_) {
NSAccessibilityPostNotification(self,
NSAccessibilityValueChangedNotification);
}
return;
}
@end
@@ -1493,6 +1560,14 @@ void BrowserWindowOsrMac::OnImeCompositionRangeChanged(
}
}
void BrowserWindowOsrMac::UpdateAccessibilityTree(CefRefPtr<CefValue> value) {
CEF_REQUIRE_UI_THREAD();
if (nsview_) {
[GLView(nsview_) UpdateAccessibilityTree:value];
}
}
void BrowserWindowOsrMac::Create(ClientWindowHandle parent_handle,
const CefRect& rect) {
REQUIRE_MAIN_THREAD();

View File

@@ -146,4 +146,11 @@ void ClientHandlerOsr::OnImeCompositionRangeChanged(
character_bounds);
}
void ClientHandlerOsr::OnAccessibilityTreeChange(CefRefPtr<CefValue> value) {
CEF_REQUIRE_UI_THREAD();
if (!osr_delegate_)
return;
osr_delegate_->UpdateAccessibilityTree(value);
}
} // namespace client

View File

@@ -13,6 +13,7 @@ namespace client {
// Client handler implementation for windowless browsers. There will only ever
// be one browser per handler instance.
class ClientHandlerOsr : public ClientHandler,
public CefAccessibilityHandler,
public CefRenderHandler {
public:
// Implement this interface to receive notification of ClientHandlerOsr
@@ -61,6 +62,8 @@ class ClientHandlerOsr : public ClientHandler,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) = 0;
virtual void UpdateAccessibilityTree(CefRefPtr<CefValue> value) = 0;
protected:
virtual ~OsrDelegate() {}
};
@@ -77,6 +80,9 @@ class ClientHandlerOsr : public ClientHandler,
CefRefPtr<CefRenderHandler> GetRenderHandler() OVERRIDE {
return this;
}
CefRefPtr<CefAccessibilityHandler> GetAccessibilityHandler() OVERRIDE {
return this;
}
// CefLifeSpanHandler methods.
void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
@@ -118,6 +124,10 @@ class ClientHandlerOsr : public ClientHandler,
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
// CefAccessibilityHandler methods.
void OnAccessibilityTreeChange(CefRefPtr<CefValue> value) OVERRIDE;
void OnAccessibilityLocationChange(CefRefPtr<CefValue> value) OVERRIDE {}
private:
// Only accessed on the UI thread.
OsrDelegate* osr_delegate_;

View File

@@ -0,0 +1,145 @@
// 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.
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
namespace client {
OsrAccessibilityHelper::OsrAccessibilityHelper(CefRefPtr<CefValue> value,
CefRefPtr<CefBrowser> browser)
: root_node_id_(-1),
focused_node_id_(-1),
browser_(browser) {
UpdateAccessibilityTree(value);
}
void OsrAccessibilityHelper::UpdateAccessibilityTree(
CefRefPtr<CefValue> value) {
if (value && value->GetType() == VTYPE_LIST) {
CefRefPtr<CefListValue > list = value->GetList();
size_t numEvents = list->GetSize();
if (numEvents > 0) {
for (size_t i = 0; i < numEvents; i++) {
CefRefPtr<CefDictionaryValue> event = list->GetDictionary(i);
if (event && event->HasKey("event_type") && event->HasKey("update")) {
std::string event_type = event->GetString("event_type");
CefRefPtr<CefDictionaryValue> update = event->GetDictionary("update");
if (event_type == "layoutComplete")
UpdateLayout(update);
if (event_type == "focus" && event->HasKey("id")) {
// Update focused node id
focused_node_id_ = event->GetInt("id");
UpdateFocusedNode(update);
}
}
}
}
}
}
void OsrAccessibilityHelper::UpdateLayout(
CefRefPtr<CefDictionaryValue> update) {
if (update) {
CefRefPtr<CefDictionaryValue> tree_data;
// get tree data
if (update->HasKey("has_tree_data") && update->GetBool("has_tree_data"))
tree_data = update->GetDictionary("tree_data");
// If a node is to be cleared
if (update->HasKey("node_id_to_clear")) {
int node_id_to_clear = update->GetInt("node_id_to_clear");
// reset root node if that is to be cleared
if (node_id_to_clear == root_node_id_)
root_node_id_ = -1;
OsrAXNode *node = GetNode(node_id_to_clear);
DestroyNode(node);
}
if (update->HasKey("root_id"))
root_node_id_ = update->GetInt("root_id");
if (tree_data && tree_data->HasKey("focus_id"))
focused_node_id_ = tree_data->GetInt("focus_id");
// Now initialize/update the node data.
if (update->HasKey("nodes")) {
CefRefPtr<CefListValue> nodes = update->GetList("nodes");
for (size_t index = 0; index < nodes->GetSize(); index++) {
CefRefPtr<CefDictionaryValue> node = nodes->GetDictionary(index);
if (node) {
int node_id = node->GetInt("id");
OsrAXNode* axNode = GetNode(node_id);
// Create if it is a new one
if (axNode) {
axNode->UpdateValue(node);
} else {
axNode = OsrAXNode::CreateNode(node, this);
accessibility_node_map_[node_id] = axNode;
}
}
}
}
}
}
void OsrAccessibilityHelper::UpdateFocusedNode(
CefRefPtr<CefDictionaryValue> update) {
if (update && update->HasKey("nodes")) {
CefRefPtr<CefListValue> nodes = update->GetList("nodes");
for (size_t index = 0; index < nodes->GetSize(); index++) {
CefRefPtr<CefDictionaryValue> node = nodes->GetDictionary(index);
if (node) {
int node_id = node->GetInt("id");
OsrAXNode* axNode = GetNode(node_id);
// Create if it is a new one
if (axNode) {
axNode->UpdateValue(node);
} else {
axNode = OsrAXNode::CreateNode(node, this);
accessibility_node_map_[node_id] = axNode;
}
}
}
}
// Now Notify Screen Reader
OsrAXNode* axNode = GetFocusedNode();
// Fallback to Root
if (!axNode)
axNode = GetRootNode();
axNode->NotifyAccessibilityEvent("focus");
}
void OsrAccessibilityHelper::Reset() {
accessibility_node_map_.clear();
root_node_id_ = focused_node_id_ = -1;
}
void OsrAccessibilityHelper::DestroyNode(OsrAXNode* node) {
if (node) {
int numChilds = node->GetChildCount();
if (numChilds > 0) {
for (int i = 0; i < numChilds; i++) {
DestroyNode(node->ChildAtIndex(i));
}
}
accessibility_node_map_.erase(node->OsrAXNodeId());
node->Destroy();
}
}
OsrAXNode* OsrAccessibilityHelper::GetNode(int nodeId) const {
if (nodeId != -1 &&
accessibility_node_map_.find(nodeId) != accessibility_node_map_.end()) {
return accessibility_node_map_.at(nodeId);
}
return NULL;
}
} // namespace client

View File

@@ -0,0 +1,63 @@
// 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.
#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_
#include <map>
#include "include/cef_browser.h"
namespace client {
class OsrAXNode;
// Helper class that abstracts Renderer Accessibility tree and provides a
// uniform interface to be consumed by IAccessible interface on Windows and
// NSAccessibility implementation on Mac in CefClient.
class OsrAccessibilityHelper {
public:
OsrAccessibilityHelper(CefRefPtr<CefValue> value,
CefRefPtr<CefBrowser> browser);
void UpdateAccessibilityTree(CefRefPtr<CefValue> value);
OsrAXNode* GetRootNode() const {
return GetNode(root_node_id_);
}
OsrAXNode* GetFocusedNode() const {
return GetNode(focused_node_id_);
}
CefWindowHandle GetWindowHandle() const {
return browser_->GetHost()->GetWindowHandle();
}
CefRefPtr<CefBrowser> GetBrowser() const {
return browser_;
};
OsrAXNode* GetNode(int nodeId) const;
private:
OsrAXNode* CreateNode(OsrAXNode* parent, CefRefPtr<CefDictionaryValue> value);
void Reset();
void UpdateLayout(CefRefPtr<CefDictionaryValue> update);
void UpdateFocusedNode(CefRefPtr<CefDictionaryValue> update);
// Destroy the node and remove from Map
void DestroyNode(OsrAXNode* node);
int root_node_id_;
int focused_node_id_;
CefRefPtr<CefBrowser> browser_;
std::map<int, OsrAXNode*> accessibility_node_map_;
};
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_HELPER_H_

View File

@@ -0,0 +1,102 @@
// 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.
// Base class implementation for CEF Acccessibility node. This is subclassed and
// used by both IAccessible/NSAccessibility protocol implementation.
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
namespace client {
OsrAXNode::OsrAXNode(CefRefPtr<CefDictionaryValue> value,
OsrAccessibilityHelper* helper)
: node_id_(-1), platform_accessibility_(NULL), parent_(NULL),
accessibility_helper_(helper) {
UpdateValue(value);
}
void OsrAXNode::UpdateValue(CefRefPtr<CefDictionaryValue> value) {
if (value && value->HasKey("id")) {
node_id_ = value->GetInt("id");
if (value->HasKey("role"))
role_ = value->GetString("role");
if (value->HasKey("child_ids")) {
CefRefPtr<CefListValue> childs = value->GetList("child_ids");
// Reset child Ids
child_ids_.clear();
for(size_t idx = 0; idx < childs->GetSize(); idx++)
child_ids_.push_back(childs->GetInt(idx));
}
// Update Location
if (value->HasKey("location")) {
CefRefPtr<CefDictionaryValue> loc = value->GetDictionary("location");
if (loc) {
location_ = CefRect(loc->GetDouble("x"), loc->GetDouble("y"),
loc->GetDouble("width"), loc->GetDouble("height"));
}
}
// Update offsets
if (value->HasKey("offset_container_id")) {
offset_container_id_ = value->GetInt("offset_container_id");
}
// Update attributes
if (value->HasKey("attributes")) {
attributes_ = value->GetDictionary("attributes");
if (attributes_ && attributes_->HasKey("name"))
name_ = attributes_->GetString("name");
if (attributes_ && attributes_->HasKey("value"))
value_ = attributes_->GetString("value");
if (attributes_ && attributes_->HasKey("description"))
description_ = attributes_->GetString("description");
}
}
}
CefWindowHandle OsrAXNode::GetWindowHandle() const {
if (accessibility_helper_)
return accessibility_helper_->GetWindowHandle();
return NULL;
}
CefRefPtr<CefBrowser> OsrAXNode::GetBrowser() const {
if (accessibility_helper_)
return accessibility_helper_->GetBrowser();
return NULL;
}
void OsrAXNode::SetParent(OsrAXNode* parent) {
parent_ = parent;
}
CefRect OsrAXNode::AxLocation() const {
CefRect loc = location_;
OsrAXNode* offsetNode = accessibility_helper_->GetNode(offset_container_id_);
// Add offset from parent Lcoation
if (offsetNode) {
CefRect offset = offsetNode->AxLocation();
loc.x += offset.x;
loc.y += offset.y;
}
return loc;
}
OsrAXNode* OsrAXNode::ChildAtIndex(int index) const {
if (index < GetChildCount())
return accessibility_helper_->GetNode(child_ids_[index]);
else
return NULL;
}
// Create and return the platform specific OsrAXNode Object
OsrAXNode* OsrAXNode::CreateNode(CefRefPtr<CefDictionaryValue> value,
OsrAccessibilityHelper* helper) {
return new OsrAXNode(value, helper);
}
} // namespace client

View File

@@ -0,0 +1,116 @@
// 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.
#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_
#pragma once
#include <vector>
#include "include/cef_browser.h"
#if defined(OS_MACOSX)
#ifdef __OBJC__
@class NSObject;
#else
class NSObject;
#endif
typedef NSObject CefNativeAccessible;
#elif defined(OS_WIN)
struct IAccessible;
typedef IAccessible CefNativeAccessible;
#else
#error "Unsupported platform"
#endif
namespace client {
class OsrAccessibilityHelper;
// OsrAXNode is the base class for implementation for the NSAccessibility
// protocol for interacting with VoiceOver and other accessibility clients.
class OsrAXNode {
public:
// Create and return the platform specific OsrAXNode Object.
static OsrAXNode* CreateNode(CefRefPtr<CefDictionaryValue> value,
OsrAccessibilityHelper* helper);
// Update Value.
void UpdateValue(CefRefPtr<CefDictionaryValue> value);
// Fire a platform-specific notification that an event has occurred on
// this object.
void NotifyAccessibilityEvent(std::string event_type) const;
// Call Destroy rather than deleting this, because the subclass may
// use reference counting.
void Destroy();
// Return NSAccessibility Object for Mac/ IAccessible for Windows
CefNativeAccessible* GetNativeAccessibleObject(OsrAXNode* parent);
CefNativeAccessible* GetParentAccessibleObject() const {
return parent_? parent_->platform_accessibility_ : NULL;
}
OsrAccessibilityHelper* GetAccessibilityHelper() const {
return accessibility_helper_;
};
int GetChildCount() const {
return static_cast<int>(child_ids_.size());
}
// Return the Child at the specified index
OsrAXNode* ChildAtIndex(int index) const;
const CefString& AxRole() const {
return role_;
}
int OsrAXNodeId() const {
return node_id_;
}
const CefString& AxValue() const {
return value_;
}
const CefString& AxName() const {
return name_;
}
const CefString& AxDescription() const {
return description_;
}
CefRect AxLocation() const;
CefWindowHandle GetWindowHandle() const;
CefRefPtr<CefBrowser> GetBrowser() const;
void SetParent(OsrAXNode* parent);
protected:
OsrAXNode(CefRefPtr<CefDictionaryValue> value,
OsrAccessibilityHelper* helper);
int node_id_;
CefString role_;
CefString value_;
CefString name_;
CefString description_;
CefRect location_;
std::vector<int> child_ids_;
CefNativeAccessible* platform_accessibility_;
OsrAXNode* parent_;
int offset_container_id_;
OsrAccessibilityHelper* accessibility_helper_;
CefRefPtr<CefDictionaryValue> attributes_;
};
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_ACCESSIBILITY_NODE_H_

View File

@@ -0,0 +1,498 @@
// 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 <Cocoa/Cocoa.h>
#import <AppKit/NSAccessibility.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 == "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 ? 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 = 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 = 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 {
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(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 [parent_
accessibilityAttributeValue:NSAccessibilityWindowAttribute];
} else if ([attribute isEqualToString:
NSAccessibilityTopLevelUIElementAttribute]) {
// We're in the same top level element as our parent.
return [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 {
if (event_type == "focus") {
NSAccessibilityPostNotification(GetWindowHandle(),
NSAccessibilityFocusedUIElementChangedNotification);
} else if (event_type == "textChanged") {
NSAccessibilityPostNotification(GetWindowHandle(),
NSAccessibilityTitleChangedNotification);
} else if (event_type == "valueChanged"){
NSAccessibilityPostNotification(GetWindowHandle(),
NSAccessibilityValueChangedNotification);
} else if (event_type == "textSelectionChanged") {
NSAccessibilityPostNotification(GetWindowHandle(),
NSAccessibilityValueChangedNotification);
}
}
void OsrAXNode::Destroy() {
if (platform_accessibility_) {
NSAccessibilityPostNotification(platform_accessibility_,
NSAccessibilityUIElementDestroyedNotification);
}
delete this;
}
// Create and return NSAccessibility Implementation Object for Mac
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(
client::OsrAXNode* parent) {
if (!platform_accessibility_) {
platform_accessibility_ = [OsrAXNodeObject elementWithNode:this];
SetParent(parent);
}
return platform_accessibility_;
}
} // namespace client

View File

@@ -0,0 +1,674 @@
// 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.
// This class implements our accessible proxy object that handles moving
// data back and forth between MSAA clients and CefClient renderers.
// Sample implementation based on ui\accessibility\ax_platform_node_win.h
#include "tests/cefclient/browser/osr_accessibility_node.h"
#if defined(CEF_USE_ATL)
#include <string>
#include <atlbase.h>
#include <oleacc.h>
#include "tests/cefclient/browser/osr_accessibility_helper.h"
namespace client {
// Return CO_E_OBJNOTCONNECTED for accessible objects thar still exists but the
// window and/or object it references has been destroyed.
#define DATACHECK(node) (node) ? S_OK : CO_E_OBJNOTCONNECTED
#define VALID_CHILDID(varChild) ((varChild.vt == VT_I4))
namespace {
// Helper function to convert a rectangle from client coordinates to screen
// coordinates.
void ClientToScreen(HWND hwnd, LPRECT lpRect) {
if (lpRect) {
POINT ptTL = { lpRect->left, lpRect->top };
POINT ptBR = { lpRect->right, lpRect->bottom };
// Win32 API only provides the call for a point.
ClientToScreen(hwnd, &ptTL);
ClientToScreen(hwnd, &ptBR);
SetRect(lpRect, ptTL.x, ptTL.y, ptBR.x, ptBR.y);
}
}
// Helper function to convert to MSAARole
int AxRoleToMSAARole(const std::string& role_string) {
if (role_string == "alert")
return ROLE_SYSTEM_ALERT;
if (role_string == "application")
return ROLE_SYSTEM_APPLICATION;
if (role_string == "buttonDropDown")
return ROLE_SYSTEM_BUTTONDROPDOWN;
if (role_string == "popUpButton")
return ROLE_SYSTEM_BUTTONMENU;
if (role_string == "checkBox")
return ROLE_SYSTEM_CHECKBUTTON;
if (role_string == "comboBox")
return ROLE_SYSTEM_COMBOBOX;
if (role_string == "dialog")
return ROLE_SYSTEM_DIALOG;
if (role_string == "group")
return ROLE_SYSTEM_GROUPING;
if (role_string == "image")
return ROLE_SYSTEM_GRAPHIC;
if (role_string == "link")
return ROLE_SYSTEM_LINK;
if (role_string == "locationBar")
return ROLE_SYSTEM_GROUPING;
if (role_string == "menuBar")
return ROLE_SYSTEM_MENUBAR;
if (role_string == "menuItem")
return ROLE_SYSTEM_MENUITEM;
if (role_string == "menuListPopup")
return ROLE_SYSTEM_MENUPOPUP;
if (role_string == "tree")
return ROLE_SYSTEM_OUTLINE;
if (role_string == "treeItem")
return ROLE_SYSTEM_OUTLINEITEM;
if (role_string == "tab")
return ROLE_SYSTEM_PAGETAB;
if (role_string == "tabList")
return ROLE_SYSTEM_PAGETABLIST;
if (role_string == "pane")
return ROLE_SYSTEM_PANE;
if (role_string == "progressIndicator")
return ROLE_SYSTEM_PROGRESSBAR;
if (role_string == "button")
return ROLE_SYSTEM_PUSHBUTTON;
if (role_string == "radioButton")
return ROLE_SYSTEM_RADIOBUTTON;
if (role_string == "scrollBar")
return ROLE_SYSTEM_SCROLLBAR;
if (role_string == "splitter")
return ROLE_SYSTEM_SEPARATOR;
if (role_string == "slider")
return ROLE_SYSTEM_SLIDER;
if (role_string == "staticText")
return ROLE_SYSTEM_STATICTEXT;
if (role_string == "textField")
return ROLE_SYSTEM_TEXT;
if (role_string == "titleBar")
return ROLE_SYSTEM_TITLEBAR;
if (role_string == "toolbar")
return ROLE_SYSTEM_TOOLBAR;
if (role_string == "webView")
return ROLE_SYSTEM_GROUPING;
if (role_string == "window")
return ROLE_SYSTEM_WINDOW;
if (role_string == "client")
return ROLE_SYSTEM_CLIENT;
// This is the default role for MSAA.
return ROLE_SYSTEM_CLIENT;
}
static inline int MiddleX(const CefRect& rect) {
return rect.x + rect.width / 2;
}
static inline int MiddleY(const CefRect& rect) {
return rect.y + rect.height / 2;
}
} // namespace
struct CefIAccessible : public IAccessible {
public:
// Implement IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
//
// IAccessible methods.
//
// Retrieves the child element or child object at a given point on the screen.
STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child) override;
// Performs the object's default action.
STDMETHODIMP accDoDefaultAction(VARIANT var_id) override;
// Retrieves the specified object's current screen location.
STDMETHODIMP accLocation(LONG* x_left, LONG* y_top, LONG* width,
LONG* height, VARIANT var_id) override;
// Traverses to another UI element and retrieves the object.
STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end) override;
// Retrieves an IDispatch interface pointer for the specified child.
STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child) override;
// Retrieves the number of accessible children.
STDMETHODIMP get_accChildCount(LONG* child_count) override;
// Retrieves a string that describes the object's default action.
STDMETHODIMP get_accDefaultAction(VARIANT var_id,
BSTR* default_action) override;
// Retrieves the tooltip description.
STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override;
// Retrieves the object that has the keyboard focus.
STDMETHODIMP get_accFocus(VARIANT* focus_child) override;
// Retrieves the specified object's shortcut.
STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id,
BSTR* access_key) override;
// Retrieves the name of the specified object.
STDMETHODIMP get_accName(VARIANT var_id, BSTR* name) override;
// Retrieves the IDispatch interface of the object's parent.
STDMETHODIMP get_accParent(IDispatch** disp_parent) override;
// Retrieves information describing the role of the specified object.
STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override;
// Retrieves the current state of the specified object.
STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override;
// Gets the help string for the specified object.
STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override;
// Retrieve or set the string value associated with the specified object.
// Setting the value is not typically used by screen readers, but it's
// used frequently by automation software.
STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override;
STDMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override;
// IAccessible methods not implemented.
STDMETHODIMP get_accSelection(VARIANT* selected) override;
STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override;
STDMETHODIMP get_accHelpTopic(BSTR* help_file, VARIANT var_id,
LONG* topic_id) override;
STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override;
// Implement IDispatch
STDMETHODIMP GetTypeInfoCount(unsigned int FAR* pctinfo);
STDMETHODIMP GetTypeInfo(unsigned int iTInfo, LCID lcid,
ITypeInfo FAR* FAR* ppTInfo);
STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames,
unsigned int cNames, LCID lcid,
DISPID FAR* rgDispId);
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult,
EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr);
CefIAccessible(OsrAXNode* node) : node_(node), ref_count_(0) {
}
// Remove the node reference when OsrAXNode is destroyed, so that
// MSAA clients get CO_E_OBJNOTCONNECTED
void MarkDestroyed() {
node_ = NULL;
}
protected:
// Ref Count
ULONG ref_count_;
// OsrAXNode* proxy object
OsrAXNode* node_;
};
// Implement IUnknown
// *********************
// Handles ref counting and querying for other supported interfaces.
// We only support, IUnknown, IDispatch and IAccessible.
STDMETHODIMP CefIAccessible::QueryInterface(REFIID riid, void** ppvObject) {
if (riid == IID_IAccessible)
*ppvObject = static_cast<IAccessible*>(this);
else if (riid == IID_IDispatch)
*ppvObject = static_cast<IDispatch*>(this);
else if (riid == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else
*ppvObject = NULL;
if (*ppvObject)
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
return (*ppvObject) ? S_OK : E_NOINTERFACE;
}
// Increments COM objects refcount required by IUnknown for reference counting
STDMETHODIMP_(ULONG) CefIAccessible::AddRef() {
return InterlockedIncrement((LONG volatile*)&ref_count_);
}
STDMETHODIMP_(ULONG) CefIAccessible::Release() {
ULONG ulRefCnt = InterlockedDecrement((LONG volatile*)&ref_count_);
if (ulRefCnt == 0) {
// Remove reference from OsrAXNode
if (node_)
node_->Destroy();
delete this;
}
return ulRefCnt;
}
// Implement IAccessible
// *********************
// Returns the parent IAccessible in the form of an IDispatch interface.
STDMETHODIMP CefIAccessible::get_accParent(IDispatch **ppdispParent) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (ppdispParent) {
CefNativeAccessible* parent = node_->GetParentAccessibleObject();
if (!parent) {
// Find our parent window
HWND hWnd = ::GetParent(node_->GetWindowHandle());
// if we have a window attempt to get its IAccessible pointer
if (hWnd) {
AccessibleObjectFromWindow(hWnd, (DWORD)OBJID_CLIENT,
IID_IAccessible, (void**)(&parent));
}
}
if (parent)
parent->AddRef();
*ppdispParent = parent;
retCode = (*ppdispParent) ? S_OK : S_FALSE;
}
}
else {
retCode = E_INVALIDARG;
}
return retCode;
}
// Returns the number of children we have for this element.
STDMETHODIMP CefIAccessible::get_accChildCount(long *pcountChildren) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode) && pcountChildren) {
// Get Child node count for this from Accessibility tree
*pcountChildren = node_->GetChildCount();
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
// Returns a child IAccessible object.
STDMETHODIMP CefIAccessible::get_accChild(VARIANT varChild,
IDispatch **ppdispChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
int numChilds = node_->GetChildCount();
// Mark Leaf node if there are no child
if (numChilds <= 0) {
*ppdispChild = NULL;
return S_FALSE;
} else {
if (ppdispChild && VALID_CHILDID(varChild)) {
if (varChild.lVal == CHILDID_SELF) {
*ppdispChild = this;
} else {
// Convert to 0 based index and get Child Node.
OsrAXNode* child = node_->ChildAtIndex(varChild.lVal - 1);
// Fallback to focused node
if (!child)
child = node_->GetAccessibilityHelper()->GetFocusedNode();
*ppdispChild = child->GetNativeAccessibleObject(node_);
}
if (*ppdispChild == NULL)
retCode = S_FALSE;
else
(*ppdispChild)->AddRef();
}
}
}
return retCode;
}
// Check and returns the accessible name for element from accessibility tree
STDMETHODIMP CefIAccessible::get_accName(VARIANT varChild, BSTR *pszName) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszName && VALID_CHILDID(varChild)) {
std::string name = node_->AxName();
CComBSTR bstrResult(name.c_str());
*pszName = bstrResult.Detach();
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
// Check and returns the value for element from accessibility tree
STDMETHODIMP CefIAccessible::get_accValue(VARIANT varChild, BSTR *pszValue) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszValue && VALID_CHILDID(varChild)) {
std::string name = node_->AxValue();
CComBSTR bstrResult(name.c_str());
*pszValue = bstrResult.Detach();
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
// Check and returns the description for element from accessibility tree
STDMETHODIMP CefIAccessible::get_accDescription(VARIANT varChild,
BSTR* pszDescription) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszDescription && VALID_CHILDID(varChild)) {
std::string name = node_->AxDescription();
CComBSTR bstrResult(name.c_str());
*pszDescription = bstrResult.Detach();
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
// Check and returns the MSAA Role for element from accessibility tree
STDMETHODIMP CefIAccessible::get_accRole(VARIANT varChild, VARIANT *pvarRole) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
// Get the accessibilty role and Map to MSAA Role
if (pvarRole) {
pvarRole->vt = VT_I4;
pvarRole->lVal = AxRoleToMSAARole(node_->AxRole());
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
// Check and returns Accessibility State for element from accessibility tree
STDMETHODIMP CefIAccessible::get_accState(VARIANT varChild,
VARIANT *pvarState) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarState) {
pvarState->vt = VT_I4;
pvarState->lVal = (GetFocus() == node_->GetWindowHandle()) ?
STATE_SYSTEM_FOCUSED : 0;
pvarState->lVal |= STATE_SYSTEM_PRESSED;
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
// For child
if (varChild.lVal == CHILDID_SELF) {
DWORD dwStyle = GetWindowLong(node_->GetWindowHandle(), GWL_STYLE);
pvarState->lVal |= ((dwStyle & WS_VISIBLE) == 0) ?
STATE_SYSTEM_INVISIBLE : 0;
pvarState->lVal |= ((dwStyle & WS_DISABLED) > 0) ?
STATE_SYSTEM_UNAVAILABLE : 0;
}
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
// Check and returns Accessibility Shortcut if any for element
STDMETHODIMP CefIAccessible::get_accKeyboardShortcut(VARIANT varChild,
BSTR* pszKeyboardShortcut) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszKeyboardShortcut && VALID_CHILDID(varChild))
*pszKeyboardShortcut = ::SysAllocString(L"None");
else
retCode = E_INVALIDARG;
}
return retCode;
}
// Return focused element from the accessibility tree
STDMETHODIMP CefIAccessible::get_accFocus(VARIANT *pFocusChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
OsrAXNode* focusedNode = node_->GetAccessibilityHelper()->GetFocusedNode();
CefNativeAccessible* nativeObj = NULL;
if (focusedNode)
nativeObj = focusedNode->GetNativeAccessibleObject(NULL);
if (nativeObj) {
if (nativeObj == this) {
pFocusChild->vt = VT_I4;
pFocusChild->lVal = CHILDID_SELF;
}
else {
pFocusChild->vt = VT_DISPATCH;
pFocusChild->pdispVal = nativeObj;
pFocusChild->pdispVal->AddRef();
}
} else {
pFocusChild->vt = VT_EMPTY;
}
}
return retCode;
}
// Return a selection list for multiple selection items.
STDMETHODIMP CefIAccessible::get_accSelection(VARIANT *pvarChildren) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarChildren)
pvarChildren->vt = VT_EMPTY;
else
retCode = E_INVALIDARG;
}
return retCode;
}
// Return a string description of the default action of our element, eg. push
STDMETHODIMP CefIAccessible::get_accDefaultAction(VARIANT varChild,
BSTR* pszDefaultAction) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszDefaultAction && VALID_CHILDID(varChild))
*pszDefaultAction = ::SysAllocString(L"Push");
else
retCode = E_INVALIDARG;
}
return retCode;
}
// child item selectionor for an item to take focus.
STDMETHODIMP CefIAccessible::accSelect(long flagsSelect, VARIANT varChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (VALID_CHILDID(varChild)) {
HWND hwnd = node_->GetWindowHandle();
// we only support SELFLAG_TAKEFOCUS.
if (((flagsSelect & SELFLAG_TAKEFOCUS) > 0) && (GetFocus() == hwnd)) {
RECT rcWnd;
GetClientRect(hwnd, &rcWnd);
InvalidateRect(hwnd, &rcWnd, FALSE);
} else {
retCode = S_FALSE;
}
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
// Returns back the screen coordinates of our element or one of its childs
STDMETHODIMP CefIAccessible::accLocation(long* pxLeft, long* pyTop,
long* pcxWidth, long* pcyHeight,
VARIANT varChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pxLeft && pyTop && pcxWidth && pcyHeight && VALID_CHILDID(varChild)) {
CefRect loc = node_->AxLocation();
RECT rcItem = { loc.x, loc.y, loc.x + loc.width, loc.y + loc.height };
HWND hwnd = node_->GetWindowHandle();
ClientToScreen(hwnd, &rcItem);
*pxLeft = rcItem.left;
*pyTop = rcItem.top;
*pcxWidth = rcItem.right - rcItem.left;
*pcyHeight = rcItem.bottom - rcItem.top;
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
// Allow clients to move the keyboard focus within the control
// Deprecated
STDMETHODIMP CefIAccessible::accNavigate(long navDir, VARIANT varStart,
VARIANT* pvarEndUpAt) {
return E_NOTIMPL;
}
// Check if the coordinates provided are within our element or child items.
STDMETHODIMP CefIAccessible::accHitTest(long xLeft, long yTop,
VARIANT *pvarChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarChild) {
pvarChild->vt = VT_EMPTY;
CefRect loc = node_->AxLocation();
RECT rcItem = { loc.x, loc.y, loc.x + loc.width, loc.y + loc.height };
POINT pt = { xLeft, yTop };
ClientToScreen(node_->GetWindowHandle(), &rcItem);
if (PtInRect(&rcItem, pt)) {
pvarChild->vt = VT_I4;
pvarChild->lVal = 1;
}
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
// Forces the default action of our element. In simplest cases, send a click.
STDMETHODIMP CefIAccessible::accDoDefaultAction(VARIANT varChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode) && VALID_CHILDID(varChild)) {
// doing our default action for out button is to simply click the button.
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, MBT_LEFT, false, 1);
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, true, 1);
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
// Set the name for an element in the accessibility tree
STDMETHODIMP CefIAccessible::put_accName(VARIANT varChild, BSTR szName) {
return E_NOTIMPL;
}
// Set the value for an element in the accessibility tree
STDMETHODIMP CefIAccessible::put_accValue(VARIANT varChild, BSTR szValue) {
return E_NOTIMPL;
}
// Return E_NOTIMPL as no help file/ topic
STDMETHODIMP CefIAccessible::get_accHelp(VARIANT varChild, BSTR *pszHelp) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::get_accHelpTopic(BSTR *pszHelpFile,
VARIANT varChild,
long* pidTopic) {
return E_NOTIMPL;
}
// IDispatch - We are not going to return E_NOTIMPL from IDispatch methods and
// let Active Accessibility implement the IAccessible interface for them.
STDMETHODIMP CefIAccessible::GetTypeInfoCount(unsigned int FAR* pctinfo) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::GetTypeInfo(unsigned int iTInfo, LCID lcid,
ITypeInfo FAR* FAR* ppTInfo) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::GetIDsOfNames(REFIID riid,
OLECHAR FAR* FAR* rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS FAR* pDispParams,
VARIANT FAR* pVarResult,
EXCEPINFO FAR* pExcepInfo,
unsigned int FAR* puArgErr) {
return E_NOTIMPL;
}
void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const {
if (event_type == "focus") {
// Notify Screen Reader of focus change
::NotifyWinEvent(EVENT_OBJECT_FOCUS, GetWindowHandle(), OBJID_CLIENT,
node_id_);
}
}
void OsrAXNode::Destroy() {
CefIAccessible* ptr = static_cast<CefIAccessible*>(platform_accessibility_);
if (ptr)
ptr->MarkDestroyed();
platform_accessibility_ = NULL;
}
// Create and return NSAccessibility Implementation Object for Window
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(OsrAXNode* parent) {
if (!platform_accessibility_) {
platform_accessibility_ = new CefIAccessible(this);
platform_accessibility_->AddRef();
SetParent(parent);
}
return platform_accessibility_;
}
} // namespace client
#else // !defined(CEF_USE_ATL)
namespace client {
void OsrAXNode::Destroy() {
}
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(OsrAXNode* parent) {
return NULL;
}
} // namespace client
#endif // !defined(CEF_USE_ATL)

View File

@@ -5,13 +5,18 @@
#include "tests/cefclient/browser/osr_window_win.h"
#include <windowsx.h>
#if defined(CEF_USE_ATL)
#include <oleacc.h>
#endif
#include "include/base/cef_build.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_ime_handler_win.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/shared/browser/util_win.h"
namespace client {
@@ -250,6 +255,8 @@ void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) {
SetUserDataPtr(hwnd_, this);
#if defined(CEF_USE_ATL)
accessibility_root_ = NULL;
// Create/register the drag&drop handler.
drop_target_ = DropTargetWin::Create(this, hwnd_);
HRESULT register_res = RegisterDragDrop(hwnd_, drop_target_);
@@ -486,7 +493,25 @@ LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd, UINT message,
self->OnIMECancelCompositionEvent();
// Let WTL call::DefWindowProc() and release its resources.
break;
#if defined(CEF_USE_ATL)
case WM_GETOBJECT: {
// Only the lower 32 bits of lParam are valid when checking the object id
// because it sometimes gets sign-extended incorrectly (but not always).
DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lParam));
// Accessibility readers will send an OBJID_CLIENT message.
if (static_cast<DWORD>(OBJID_CLIENT) == obj_id) {
if (self->accessibility_root_) {
return LresultFromObject(IID_IAccessible, wParam,
static_cast<IAccessible*>(self->accessibility_root_));
} else {
// Notify the renderer to enable accessibility.
if (self->browser_ && self->browser_->GetHost())
self->browser_->GetHost()->SetAccessibilityState(STATE_ENABLED);
}
}
} break;
#endif
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
@@ -1014,6 +1039,23 @@ void OsrWindowWin::OnImeCompositionRangeChanged(
}
}
void OsrWindowWin::UpdateAccessibilityTree(CefRefPtr<CefValue> value) {
CEF_REQUIRE_UI_THREAD();
#if defined(CEF_USE_ATL)
if (!accessibility_handler_) {
accessibility_handler_.reset(new OsrAccessibilityHelper(value, browser_));
} else {
accessibility_handler_->UpdateAccessibilityTree(value);
}
// Update |accessibility_root_| because UpdateAccessibilityTree may have
// cleared it.
OsrAXNode* root = accessibility_handler_->GetRootNode();
accessibility_root_ = root ? root->GetNativeAccessibleObject(NULL) : NULL;
#endif // defined(CEF_USE_ATL)
}
#if defined(CEF_USE_ATL)
CefBrowserHost::DragOperationsMask

View File

@@ -11,11 +11,13 @@
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/client_handler_osr.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_dragdrop_win.h"
#include "tests/cefclient/browser/osr_renderer.h"
namespace client {
class OsrAccessibilityHelper;
class OsrImeHandlerWin;
// Represents the native parent window for an off-screen browser. This object
@@ -24,7 +26,7 @@ class OsrImeHandlerWin;
class OsrWindowWin :
public base::RefCountedThreadSafe<OsrWindowWin, CefDeleteOnUIThread>,
public ClientHandlerOsr::OsrDelegate
#if defined(CEF_USE_ATL)
#if defined(CEF_USE_ATL)
, public OsrDragEvents
#endif
{
@@ -51,7 +53,7 @@ class OsrWindowWin :
const CefBrowserSettings& settings,
CefRefPtr<CefRequestContext> request_context,
const std::string& startup_url);
// Show the popup window with correct parent and bounds in parent coordinates.
void ShowPopup(HWND parent_hwnd, int x, int y, size_t width, size_t height);
@@ -145,6 +147,8 @@ class OsrWindowWin :
const CefRange& selection_range,
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
void UpdateAccessibilityTree(CefRefPtr<CefValue> value);
#if defined(CEF_USE_ATL)
// OsrDragEvents methods.
CefBrowserHost::DragOperationsMask OnDragEnter(
@@ -178,6 +182,11 @@ class OsrWindowWin :
#if defined(CEF_USE_ATL)
CComPtr<DropTargetWin> drop_target_;
CefRenderHandler::DragOperation current_drag_op_;
// Class that abstracts the accessibility information received from the
// renderer.
scoped_ptr<OsrAccessibilityHelper> accessibility_handler_;
IAccessible* accessibility_root_;
#endif
bool painting_popup_;

View File

@@ -648,6 +648,19 @@ void RootWindowMac::OnSetLoadingState(bool isLoading,
[back_button_ setEnabled:canGoBack];
[forward_button_ setEnabled:canGoForward];
}
// After Loading is done, check if voiceover is running and accessibility
// should be enabled.
if (!isLoading) {
Boolean keyExists = false;
// On OSX there is no API to query if VoiceOver is active or not. The value
// however is stored in preferences that can be queried.
if (CFPreferencesGetAppBooleanValue(CFSTR("voiceOverOnOffKey"),
CFSTR("com.apple.universalaccess"),
&keyExists)) {
GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED);
}
}
}
void RootWindowMac::NotifyDestroyedIfDone() {

View File

@@ -541,6 +541,18 @@ LRESULT CALLBACK RootWindowWin::RootWndProc(HWND hWnd, UINT message,
return 0;
break;
case WM_GETOBJECT: {
// Only the lower 32 bits of lParam are valid when checking the object id
// because it sometimes gets sign-extended incorrectly (but not always).
DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lParam));
// Accessibility readers will send an OBJID_CLIENT message.
if (static_cast<DWORD>(OBJID_CLIENT) == obj_id) {
if (self->GetBrowser() && self->GetBrowser()->GetHost())
self->GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED);
}
} break;
case WM_PAINT:
self->OnPaint();
return 0;

View File

@@ -65,6 +65,7 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) {
- (IBAction)menuTestsPrint:(id)sender;
- (IBAction)menuTestsPrintToPdf:(id)sender;
- (IBAction)menuTestsOtherTests:(id)sender;
- (void)enableAccessibility:(bool)bEnable;
@end
// Provide the CefAppProtocol implementation required by CEF.
@@ -132,6 +133,18 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) {
[delegate tryToTerminateApplication:self];
// Return, don't exit. The application is responsible for exiting on its own.
}
// Detect dynamically if VoiceOver is running. Like Chromium, rely upon the
// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set
// when VoiceOver is launched and unset when VoiceOver is closed.
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
ClientAppDelegate* delegate = static_cast<ClientAppDelegate*>(
[[NSApplication sharedApplication] delegate]);
[delegate enableAccessibility:([value intValue] == 1)];
}
return [super accessibilitySetValue:value forAttribute:attribute];
}
@end
@implementation ClientAppDelegate
@@ -297,6 +310,22 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) {
[self testsItemSelected:ID_TESTS_OTHER_TESTS];
}
- (void)enableAccessibility:(bool)bEnable {
// Retrieve the active RootWindow.
NSWindow* key_window = [[NSApplication sharedApplication] keyWindow];
if (!key_window)
return;
scoped_refptr<client::RootWindow> root_window =
client::RootWindow::GetForNSWindow(key_window);
CefRefPtr<CefBrowser> browser = root_window->GetBrowser();
if (browser.get()) {
browser->GetHost()->SetAccessibilityState(bEnable ?
STATE_ENABLED : STATE_DISABLED);
}
}
- (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication *)sender {
return NSTerminateNow;