Skip to content

Commit

Permalink
MacViews: Accessibility bridge
Browse files Browse the repository at this point in the history
Implement basic accessibility hierarchy support for MacViews, bridging the
View hierarchy to a hierarchy of Objective-C objects that implement the
NSAccessibility informal protocol.
AXPlatformNode is introduced as the accessibility abstraction that will be
used for views and browser content, for both Mac and Windows. This initial
patch includes the basic views on Mac implementation.

BUG=396137

Review URL: https://codereview.chromium.org/420653003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286768 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
andresantoso@chromium.org committed Jul 31, 2014
1 parent 52daf4f commit bacf713
Show file tree
Hide file tree
Showing 18 changed files with 757 additions and 204 deletions.
190 changes: 3 additions & 187 deletions content/browser/accessibility/browser_accessibility_cocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "content/browser/accessibility/browser_accessibility_manager_mac.h"
#include "content/public/common/content_client.h"
#include "grit/webkit_strings.h"
#import "ui/accessibility/platform/ax_platform_node_mac.h"

// See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5,
// 10.6, and 10.7. It allows accessibility clients to observe events posted on
Expand All @@ -39,197 +40,12 @@
browserAccessibility->GetStringAttribute(attribute));
}

struct MapEntry {
ui::AXRole webKitValue;
NSString* nativeValue;
};

typedef std::map<ui::AXRole, NSString*> RoleMap;

// GetState checks the bitmask used in AXNodeData to check
// if the given state was set on the accessibility object.
bool GetState(BrowserAccessibility* accessibility, ui::AXState state) {
return ((accessibility->GetState() >> state) & 1);
}

RoleMap BuildRoleMap() {
const MapEntry roles[] = {
{ ui::AX_ROLE_ALERT, NSAccessibilityGroupRole },
{ ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole },
{ ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole },
{ ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole },
{ ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole },
{ ui::AX_ROLE_BANNER, NSAccessibilityGroupRole },
{ ui::AX_ROLE_BROWSER, NSAccessibilityBrowserRole },
{ ui::AX_ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole },
{ ui::AX_ROLE_BUTTON, NSAccessibilityButtonRole },
{ ui::AX_ROLE_CANVAS, NSAccessibilityImageRole },
{ ui::AX_ROLE_CELL, @"AXCell" },
{ ui::AX_ROLE_CHECK_BOX, NSAccessibilityCheckBoxRole },
{ ui::AX_ROLE_COLOR_WELL, NSAccessibilityColorWellRole },
{ ui::AX_ROLE_COLUMN, NSAccessibilityColumnRole },
{ ui::AX_ROLE_COLUMN_HEADER, @"AXCell" },
{ ui::AX_ROLE_COMBO_BOX, NSAccessibilityComboBoxRole },
{ ui::AX_ROLE_COMPLEMENTARY, NSAccessibilityGroupRole },
{ ui::AX_ROLE_CONTENT_INFO, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DEFINITION, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DESCRIPTION_LIST_TERM, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DIALOG, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DIRECTORY, NSAccessibilityListRole },
{ ui::AX_ROLE_DISCLOSURE_TRIANGLE, NSAccessibilityDisclosureTriangleRole },
{ ui::AX_ROLE_DIV, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DOCUMENT, NSAccessibilityGroupRole },
{ ui::AX_ROLE_DRAWER, NSAccessibilityDrawerRole },
{ ui::AX_ROLE_EDITABLE_TEXT, NSAccessibilityTextFieldRole },
{ ui::AX_ROLE_FOOTER, NSAccessibilityGroupRole },
{ ui::AX_ROLE_FORM, NSAccessibilityGroupRole },
{ ui::AX_ROLE_GRID, NSAccessibilityGridRole },
{ ui::AX_ROLE_GROUP, NSAccessibilityGroupRole },
{ ui::AX_ROLE_GROW_AREA, NSAccessibilityGrowAreaRole },
{ ui::AX_ROLE_HEADING, @"AXHeading" },
{ ui::AX_ROLE_HELP_TAG, NSAccessibilityHelpTagRole },
{ ui::AX_ROLE_HORIZONTAL_RULE, NSAccessibilityGroupRole },
{ ui::AX_ROLE_IFRAME, NSAccessibilityGroupRole },
{ ui::AX_ROLE_IGNORED, NSAccessibilityUnknownRole },
{ ui::AX_ROLE_IMAGE, NSAccessibilityImageRole },
{ ui::AX_ROLE_IMAGE_MAP, NSAccessibilityGroupRole },
{ ui::AX_ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole },
{ ui::AX_ROLE_INCREMENTOR, NSAccessibilityIncrementorRole },
{ ui::AX_ROLE_LABEL_TEXT, NSAccessibilityGroupRole },
{ ui::AX_ROLE_LINK, NSAccessibilityLinkRole },
{ ui::AX_ROLE_LIST, NSAccessibilityListRole },
{ ui::AX_ROLE_LIST_BOX, NSAccessibilityListRole },
{ ui::AX_ROLE_LIST_BOX_OPTION, NSAccessibilityStaticTextRole },
{ ui::AX_ROLE_LIST_ITEM, NSAccessibilityGroupRole },
{ ui::AX_ROLE_LIST_MARKER, @"AXListMarker" },
{ ui::AX_ROLE_LOG, NSAccessibilityGroupRole },
{ ui::AX_ROLE_MAIN, NSAccessibilityGroupRole },
{ ui::AX_ROLE_MARQUEE, NSAccessibilityGroupRole },
{ ui::AX_ROLE_MATH, NSAccessibilityGroupRole },
{ ui::AX_ROLE_MATTE, NSAccessibilityMatteRole },
{ ui::AX_ROLE_MENU, NSAccessibilityMenuRole },
{ ui::AX_ROLE_MENU_BAR, NSAccessibilityMenuBarRole },
{ ui::AX_ROLE_MENU_BUTTON, NSAccessibilityButtonRole },
{ ui::AX_ROLE_MENU_ITEM, NSAccessibilityMenuItemRole },
{ ui::AX_ROLE_MENU_LIST_OPTION, NSAccessibilityMenuItemRole },
{ ui::AX_ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole },
{ ui::AX_ROLE_NAVIGATION, NSAccessibilityGroupRole },
{ ui::AX_ROLE_NOTE, NSAccessibilityGroupRole },
{ ui::AX_ROLE_OUTLINE, NSAccessibilityOutlineRole },
{ ui::AX_ROLE_PARAGRAPH, NSAccessibilityGroupRole },
{ ui::AX_ROLE_POP_UP_BUTTON, NSAccessibilityPopUpButtonRole },
{ ui::AX_ROLE_PRESENTATIONAL, NSAccessibilityGroupRole },
{ ui::AX_ROLE_PROGRESS_INDICATOR, NSAccessibilityProgressIndicatorRole },
{ ui::AX_ROLE_RADIO_BUTTON, NSAccessibilityRadioButtonRole },
{ ui::AX_ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole },
{ ui::AX_ROLE_REGION, NSAccessibilityGroupRole },
{ ui::AX_ROLE_ROOT_WEB_AREA, @"AXWebArea" },
{ ui::AX_ROLE_ROW, NSAccessibilityRowRole },
{ ui::AX_ROLE_ROW_HEADER, @"AXCell" },
{ ui::AX_ROLE_RULER, NSAccessibilityRulerRole },
{ ui::AX_ROLE_RULER_MARKER, NSAccessibilityRulerMarkerRole },
{ ui::AX_ROLE_SCROLL_BAR, NSAccessibilityScrollBarRole },
{ ui::AX_ROLE_SEARCH, NSAccessibilityGroupRole },
{ ui::AX_ROLE_SHEET, NSAccessibilitySheetRole },
{ ui::AX_ROLE_SLIDER, NSAccessibilitySliderRole },
{ ui::AX_ROLE_SLIDER_THUMB, NSAccessibilityValueIndicatorRole },
{ ui::AX_ROLE_SPIN_BUTTON, NSAccessibilitySliderRole },
{ ui::AX_ROLE_SPLITTER, NSAccessibilitySplitterRole },
{ ui::AX_ROLE_SPLIT_GROUP, NSAccessibilitySplitGroupRole },
{ ui::AX_ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole },
{ ui::AX_ROLE_STATUS, NSAccessibilityGroupRole },
{ ui::AX_ROLE_SVG_ROOT, NSAccessibilityGroupRole },
{ ui::AX_ROLE_SYSTEM_WIDE, NSAccessibilityUnknownRole },
{ ui::AX_ROLE_TAB, NSAccessibilityRadioButtonRole },
{ ui::AX_ROLE_TABLE, NSAccessibilityTableRole },
{ ui::AX_ROLE_TABLE_HEADER_CONTAINER, NSAccessibilityGroupRole },
{ ui::AX_ROLE_TAB_LIST, NSAccessibilityTabGroupRole },
{ ui::AX_ROLE_TAB_PANEL, NSAccessibilityGroupRole },
{ ui::AX_ROLE_TEXT_AREA, NSAccessibilityTextAreaRole },
{ ui::AX_ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole },
{ ui::AX_ROLE_TIMER, NSAccessibilityGroupRole },
{ ui::AX_ROLE_TOGGLE_BUTTON, NSAccessibilityCheckBoxRole },
{ ui::AX_ROLE_TOOLBAR, NSAccessibilityToolbarRole },
{ ui::AX_ROLE_TOOLTIP, NSAccessibilityGroupRole },
{ ui::AX_ROLE_TREE, NSAccessibilityOutlineRole },
{ ui::AX_ROLE_TREE_GRID, NSAccessibilityTableRole },
{ ui::AX_ROLE_TREE_ITEM, NSAccessibilityRowRole },
{ ui::AX_ROLE_VALUE_INDICATOR, NSAccessibilityValueIndicatorRole },
{ ui::AX_ROLE_WEB_AREA, @"AXWebArea" },
{ ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole },

// TODO(dtseng): we don't correctly support the attributes for these roles.
// { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole },
};

RoleMap role_map;
for (size_t i = 0; i < arraysize(roles); ++i)
role_map[roles[i].webKitValue] = roles[i].nativeValue;
return role_map;
}

// A mapping of webkit roles to native roles.
NSString* NativeRoleFromAXRole(
const ui::AXRole& role) {
CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_role,
(BuildRoleMap()));
RoleMap::iterator it = web_accessibility_to_native_role.find(role);
if (it != web_accessibility_to_native_role.end())
return it->second;
else
return NSAccessibilityUnknownRole;
}

RoleMap BuildSubroleMap() {
const MapEntry subroles[] = {
{ ui::AX_ROLE_ALERT, @"AXApplicationAlert" },
{ ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog" },
{ ui::AX_ROLE_ARTICLE, @"AXDocumentArticle" },
{ ui::AX_ROLE_DEFINITION, @"AXDefinition" },
{ ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDescription" },
{ ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm" },
{ ui::AX_ROLE_DIALOG, @"AXApplicationDialog" },
{ ui::AX_ROLE_DOCUMENT, @"AXDocument" },
{ ui::AX_ROLE_FOOTER, @"AXLandmarkContentInfo" },
{ ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication" },
{ ui::AX_ROLE_BANNER, @"AXLandmarkBanner" },
{ ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary" },
{ ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo" },
{ ui::AX_ROLE_MAIN, @"AXLandmarkMain" },
{ ui::AX_ROLE_NAVIGATION, @"AXLandmarkNavigation" },
{ ui::AX_ROLE_SEARCH, @"AXLandmarkSearch" },
{ ui::AX_ROLE_LOG, @"AXApplicationLog" },
{ ui::AX_ROLE_MARQUEE, @"AXApplicationMarquee" },
{ ui::AX_ROLE_MATH, @"AXDocumentMath" },
{ ui::AX_ROLE_NOTE, @"AXDocumentNote" },
{ ui::AX_ROLE_REGION, @"AXDocumentRegion" },
{ ui::AX_ROLE_STATUS, @"AXApplicationStatus" },
{ ui::AX_ROLE_TAB_PANEL, @"AXTabPanel" },
{ ui::AX_ROLE_TIMER, @"AXApplicationTimer" },
{ ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton" },
{ ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip" },
{ ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole },
};

RoleMap subrole_map;
for (size_t i = 0; i < arraysize(subroles); ++i)
subrole_map[subroles[i].webKitValue] = subroles[i].nativeValue;
return subrole_map;
}

// A mapping of webkit roles to native subroles.
NSString* NativeSubroleFromAXRole(
const ui::AXRole& role) {
CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_subrole,
(BuildSubroleMap()));
RoleMap::iterator it = web_accessibility_to_native_subrole.find(role);
if (it != web_accessibility_to_native_subrole.end())
return it->second;
else
return nil;
}

// A mapping from an accessibility attribute to its method name.
NSDictionary* attributeToMethodNameMap = nil;

Expand Down Expand Up @@ -719,7 +535,7 @@ - (NSString*)role {
else
return NSAccessibilityButtonRole;
}
return NativeRoleFromAXRole(role);
return [AXPlatformNodeCocoa nativeRoleFromAXRole:role];
}

// Returns a string indicating the role description of this object.
Expand Down Expand Up @@ -895,7 +711,7 @@ - (NSString*) subrole {
}
}

return NativeSubroleFromAXRole(browserAccessibilityRole);
return [AXPlatformNodeCocoa nativeSubroleFromAXRole:browserAccessibilityRole];
}

// Returns all tabs in this subtree.
Expand Down
7 changes: 7 additions & 0 deletions ui/accessibility/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ component("accessibility") {
"ax_tree_update.h",
"ax_view_state.cc",
"ax_view_state.h",
"platform/ax_platform_node.cc",
"platform/ax_platform_node.h",
"platform/ax_platform_node_base.cc",
"platform/ax_platform_node_base.h",
"platform/ax_platform_node_delegate.h",
"platform/ax_platform_node_mac.h",
"platform/ax_platform_node_mac.mm",
]

defines = [ "ACCESSIBILITY_IMPLEMENTATION" ]
Expand Down
7 changes: 7 additions & 0 deletions ui/accessibility/accessibility.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@
'ax_tree_update.h',
'ax_view_state.cc',
'ax_view_state.h',
'platform/ax_platform_node.cc',
'platform/ax_platform_node.h',
'platform/ax_platform_node_base.cc',
'platform/ax_platform_node_base.h',
'platform/ax_platform_node_delegate.h',
'platform/ax_platform_node_mac.h',
'platform/ax_platform_node_mac.mm',
]
},
{
Expand Down
25 changes: 25 additions & 0 deletions ui/accessibility/platform/ax_platform_node.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2014 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 "ui/accessibility/platform/ax_platform_node.h"

#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"

namespace ui {

#if !defined(OS_MACOSX)
// static
AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) {
return NULL;
}
#endif

AXPlatformNode::AXPlatformNode() {
}

AXPlatformNode::~AXPlatformNode() {
}

} // namespace ui
33 changes: 33 additions & 0 deletions ui/accessibility/platform/ax_platform_node.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2014 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 UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_H_
#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_H_

#include "ui/accessibility/ax_export.h"
#include "ui/gfx/native_widget_types.h"

namespace ui {

class AXPlatformNodeDelegate;

class AX_EXPORT AXPlatformNode {
public:
// Create a platform appropriate instance.
static AXPlatformNode* Create(AXPlatformNodeDelegate* delegate);

// Call Destroy rather than deleting this, because the subclass may
// use reference counting.
virtual void Destroy() = 0;

virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0;

protected:
AXPlatformNode();
virtual ~AXPlatformNode();
};

} // namespace ui

#endif // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_H_
58 changes: 58 additions & 0 deletions ui/accessibility/platform/ax_platform_node_base.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2014 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 "ui/accessibility/platform/ax_platform_node_base.h"

#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"

namespace ui {

AXPlatformNodeBase::AXPlatformNodeBase() {
}

AXPlatformNodeBase::~AXPlatformNodeBase() {
}

void AXPlatformNodeBase::Init(AXPlatformNodeDelegate* delegate) {
delegate_ = delegate;
}

AXRole AXPlatformNodeBase::GetRole() const {
return delegate_ ? delegate_->GetData()->role : AX_ROLE_UNKNOWN;
}

gfx::Rect AXPlatformNodeBase::GetBoundsInScreen() const {
if (!delegate_)
return gfx::Rect();
gfx::Rect bounds = delegate_->GetData()->location;
bounds.Offset(delegate_->GetGlobalCoordinateOffset());
return bounds;
}

gfx::NativeViewAccessible AXPlatformNodeBase::GetParent() {
return delegate_ ? delegate_->GetParent() : NULL;
}

int AXPlatformNodeBase::GetChildCount() {
return delegate_ ? delegate_->GetChildCount() : 0;
}

gfx::NativeViewAccessible AXPlatformNodeBase::ChildAtIndex(int index) {
return delegate_ ? delegate_->ChildAtIndex(index) : NULL;
}

// AXPlatformNode

void AXPlatformNodeBase::Destroy() {
delegate_ = NULL;
delete this;
}

gfx::NativeViewAccessible AXPlatformNodeBase::GetNativeViewAccessible() {
return NULL;
}


} // namespace ui
Loading

0 comments on commit bacf713

Please sign in to comment.