mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
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:
committed by
Marshall Greenblatt
parent
64fcfa6068
commit
816f700d3e
@@ -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_);
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -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_;
|
||||
|
145
tests/cefclient/browser/osr_accessibility_helper.cc
Normal file
145
tests/cefclient/browser/osr_accessibility_helper.cc
Normal 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
|
63
tests/cefclient/browser/osr_accessibility_helper.h
Normal file
63
tests/cefclient/browser/osr_accessibility_helper.h
Normal 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_
|
102
tests/cefclient/browser/osr_accessibility_node.cc
Normal file
102
tests/cefclient/browser/osr_accessibility_node.cc
Normal 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
|
116
tests/cefclient/browser/osr_accessibility_node.h
Normal file
116
tests/cefclient/browser/osr_accessibility_node.h
Normal 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_
|
498
tests/cefclient/browser/osr_accessibility_node_mac.mm
Normal file
498
tests/cefclient/browser/osr_accessibility_node_mac.mm
Normal 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
|
674
tests/cefclient/browser/osr_accessibility_node_win.cc
Normal file
674
tests/cefclient/browser/osr_accessibility_node_win.cc
Normal 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)
|
@@ -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
|
||||
|
@@ -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_;
|
||||
|
@@ -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() {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user