Commit b8b88532 authored by dmazzoni@chromium.org's avatar dmazzoni@chromium.org

Expose an accessible relation between the main window and active alert.

This will specifically allow screen readers to query the accessible
object for the main window and quickly get a list of an alerts that
are visible - for example infobars, bubbles, and the Chrome menu itself
if it has an alert icon in it.

BUG=369903

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270074 0039d316-1c4b-4281-b951-d872f2087c98
parent d026e687
......@@ -189,6 +189,7 @@ void AccessibleWebViewRegistry::QueryIAccessible2Interface(View* web_view) {
long NativeViewAccessibilityWin::next_unique_id_ = 1;
int NativeViewAccessibilityWin::view_storage_ids_[kMaxViewStorageIds] = {0};
int NativeViewAccessibilityWin::next_view_storage_id_index_ = 0;
std::vector<int> NativeViewAccessibilityWin::alert_target_view_storage_ids_;
// static
NativeViewAccessibility* NativeViewAccessibility::Create(View* view) {
......@@ -210,6 +211,7 @@ NativeViewAccessibilityWin::NativeViewAccessibilityWin()
}
NativeViewAccessibilityWin::~NativeViewAccessibilityWin() {
RemoveAlertTarget();
}
void NativeViewAccessibilityWin::NotifyAccessibilityEvent(
......@@ -236,6 +238,10 @@ void NativeViewAccessibilityWin::NotifyAccessibilityEvent(
::NotifyWinEvent(MSAAEvent(event_type), hwnd, OBJID_CLIENT, child_id);
next_view_storage_id_index_ =
(next_view_storage_id_index_ + 1) % kMaxViewStorageIds;
// Keep track of views that are a target of an alert event.
if (event_type == ui::AX_EVENT_ALERT)
AddAlertTarget();
}
gfx::NativeViewAccessible NativeViewAccessibilityWin::GetNativeObject() {
......@@ -865,6 +871,59 @@ STDMETHODIMP NativeViewAccessibilityWin::get_windowHandle(HWND* window_handle) {
return *window_handle ? S_OK : S_FALSE;
}
STDMETHODIMP NativeViewAccessibilityWin::get_relationTargetsOfType(
BSTR type_bstr,
long max_targets,
IUnknown ***targets,
long *n_targets) {
if (!view_)
return E_FAIL;
if (!targets || !n_targets)
return E_INVALIDARG;
*n_targets = 0;
*targets = NULL;
// Only respond to requests for relations of type "alerts" on the
// root view.
base::string16 type(type_bstr);
if (type != L"alerts" || view_->parent())
return S_FALSE;
// Collect all of the alert views that are still valid.
std::vector<View*> alert_views;
ViewStorage* view_storage = ViewStorage::GetInstance();
for (size_t i = 0; i < alert_target_view_storage_ids_.size(); ++i) {
int view_storage_id = alert_target_view_storage_ids_[i];
View* view = view_storage->RetrieveView(view_storage_id);
if (!view || !view_->Contains(view))
continue;
alert_views.push_back(view);
}
long count = alert_views.size();
if (count == 0)
return S_FALSE;
// Don't return more targets than max_targets - but note that the caller
// is allowed to specify max_targets=0 to mean no limit.
if (max_targets > 0 && count > max_targets)
count = max_targets;
// Return the number of targets.
*n_targets = count;
// Allocate COM memory for the result array and populate it.
*targets = static_cast<IUnknown**>(
CoTaskMemAlloc(count * sizeof(IUnknown*)));
for (long i = 0; i < count; ++i) {
(*targets)[i] = alert_views[i]->GetNativeViewAccessible();
(*targets)[i]->AddRef();
}
return S_OK;
}
//
// IAccessibleText
//
......@@ -1071,11 +1130,12 @@ STDMETHODIMP NativeViewAccessibilityWin::QueryService(
if (!view_)
return E_FAIL;
if (riid == IID_IAccessible2)
if (riid == IID_IAccessible2 || riid == IID_IAccessible2_2)
AccessibleWebViewRegistry::GetInstance()->EnableIAccessible2Support();
if (guidService == IID_IAccessible ||
guidService == IID_IAccessible2 ||
guidService == IID_IAccessible2_2 ||
guidService == IID_IAccessibleText) {
return QueryInterface(riid, object);
}
......@@ -1412,4 +1472,32 @@ void NativeViewAccessibilityWin::PopulateChildWidgetVector(
}
}
void NativeViewAccessibilityWin::AddAlertTarget() {
ViewStorage* view_storage = ViewStorage::GetInstance();
for (size_t i = 0; i < alert_target_view_storage_ids_.size(); ++i) {
int view_storage_id = alert_target_view_storage_ids_[i];
View* view = view_storage->RetrieveView(view_storage_id);
if (view == view_)
return;
}
int view_storage_id = view_storage->CreateStorageID();
view_storage->StoreView(view_storage_id, view_);
alert_target_view_storage_ids_.push_back(view_storage_id);
}
void NativeViewAccessibilityWin::RemoveAlertTarget() {
ViewStorage* view_storage = ViewStorage::GetInstance();
size_t i = 0;
while (i < alert_target_view_storage_ids_.size()) {
int view_storage_id = alert_target_view_storage_ids_[i];
View* view = view_storage->RetrieveView(view_storage_id);
if (view == NULL || view == view_) {
alert_target_view_storage_ids_.erase(
alert_target_view_storage_ids_.begin() + i);
} else {
++i;
}
}
}
} // namespace views
......@@ -12,6 +12,7 @@
#include <UIAutomationCore.h>
#include <set>
#include <vector>
#include "third_party/iaccessible2/ia2_api_all.h"
#include "ui/accessibility/ax_view_state.h"
......@@ -38,7 +39,7 @@ namespace views {
class __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
NativeViewAccessibilityWin
: public CComObjectRootEx<CComMultiThreadModel>,
public IDispatchImpl<IAccessible2, &IID_IAccessible2,
public IDispatchImpl<IAccessible2_2, &IID_IAccessible2_2,
&LIBID_IAccessible2Lib>,
public IAccessibleText,
public IServiceProvider,
......@@ -47,9 +48,9 @@ NativeViewAccessibilityWin
public NativeViewAccessibility {
public:
BEGIN_COM_MAP(NativeViewAccessibilityWin)
COM_INTERFACE_ENTRY2(IDispatch, IAccessible2)
COM_INTERFACE_ENTRY2(IAccessible, IAccessible2)
COM_INTERFACE_ENTRY(IAccessible2)
COM_INTERFACE_ENTRY2(IDispatch, IAccessible2_2)
COM_INTERFACE_ENTRY2(IAccessible, IAccessible2_2)
COM_INTERFACE_ENTRY(IAccessible2_2)
COM_INTERFACE_ENTRY(IAccessibleText)
COM_INTERFACE_ENTRY(IServiceProvider)
COM_INTERFACE_ENTRY(IAccessibleEx)
......@@ -145,6 +146,11 @@ NativeViewAccessibilityWin
STDMETHODIMP get_windowHandle(HWND* window_handle);
STDMETHODIMP get_relationTargetsOfType(BSTR type,
long max_targets,
IUnknown ***targets,
long *n_targets);
//
// IAccessible2 methods not implemented.
//
......@@ -152,6 +158,9 @@ NativeViewAccessibilityWin
STDMETHODIMP get_attributes(BSTR* attributes) {
return E_NOTIMPL;
}
STDMETHODIMP get_attribute(BSTR name, VARIANT* attribute) {
return E_NOTIMPL;
}
STDMETHODIMP get_indexInParent(LONG* index_in_parent) {
return E_NOTIMPL;
}
......@@ -205,6 +214,10 @@ NativeViewAccessibilityWin
STDMETHODIMP get_locale(IA2Locale* locale) {
return E_NOTIMPL;
}
STDMETHODIMP get_accessibleWithCaret(IUnknown** accessible,
long* caret_offset) {
return E_NOTIMPL;
}
//
// IAccessibleText methods.
......@@ -396,6 +409,12 @@ NativeViewAccessibilityWin
// NativeViewHost.
void PopulateChildWidgetVector(std::vector<Widget*>* child_widgets);
// Adds this view to alert_target_view_storage_ids_.
void AddAlertTarget();
// Removes this view from alert_target_view_storage_ids_.
void RemoveAlertTarget();
// Give CComObject access to the class constructor.
template <class Base> friend class CComObject;
......@@ -418,6 +437,11 @@ NativeViewAccessibilityWin
// Next index into |view_storage_ids_| to use.
static int next_view_storage_id_index_;
// A vector of view storage ids of views that have been the target of
// an alert event, in order to provide an api to quickly identify all
// open alerts.
static std::vector<int> alert_target_view_storage_ids_;
DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin);
};
......
......@@ -7,14 +7,33 @@
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.h"
#include "third_party/iaccessible2/ia2_api_all.h"
#include "ui/views/accessibility/native_view_accessibility.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_base.h"
using base::win::ScopedBstr;
using base::win::ScopedComPtr;
using base::win::ScopedVariant;
namespace views {
namespace test {
typedef ViewsTestBase NativeViewAcccessibilityWinTest;
class NativeViewAcccessibilityWinTest : public ViewsTestBase {
public:
NativeViewAcccessibilityWinTest() {}
virtual ~NativeViewAcccessibilityWinTest() {}
protected:
void GetIAccessible2InterfaceForView(View* view, IAccessible2_2** result) {
ScopedComPtr<IAccessible> view_accessible(
view->GetNativeViewAccessible());
ScopedComPtr<IServiceProvider> service_provider;
ASSERT_EQ(S_OK, view_accessible.QueryInterface(service_provider.Receive()));
ASSERT_EQ(S_OK,
service_provider->QueryService(IID_IAccessible2_2, result));
}
};
TEST_F(NativeViewAcccessibilityWinTest, TextfieldAccessibility) {
Widget widget;
......@@ -31,32 +50,32 @@ TEST_F(NativeViewAcccessibilityWinTest, TextfieldAccessibility) {
textfield->SetText(L"Value");
content->AddChildView(textfield);
base::win::ScopedComPtr<IAccessible> content_accessible(
ScopedComPtr<IAccessible> content_accessible(
content->GetNativeViewAccessible());
LONG child_count = 0;
ASSERT_EQ(S_OK, content_accessible->get_accChildCount(&child_count));
ASSERT_EQ(1L, child_count);
base::win::ScopedComPtr<IDispatch> textfield_dispatch;
base::win::ScopedComPtr<IAccessible> textfield_accessible;
base::win::ScopedVariant child_index(1);
ScopedComPtr<IDispatch> textfield_dispatch;
ScopedComPtr<IAccessible> textfield_accessible;
ScopedVariant child_index(1);
ASSERT_EQ(S_OK, content_accessible->get_accChild(
child_index, textfield_dispatch.Receive()));
ASSERT_EQ(S_OK, textfield_dispatch.QueryInterface(
textfield_accessible.Receive()));
base::win::ScopedBstr name;
base::win::ScopedVariant childid_self(CHILDID_SELF);
ScopedBstr name;
ScopedVariant childid_self(CHILDID_SELF);
ASSERT_EQ(S_OK, textfield_accessible->get_accName(
childid_self, name.Receive()));
ASSERT_STREQ(L"Name", name);
base::win::ScopedBstr value;
ScopedBstr value;
ASSERT_EQ(S_OK, textfield_accessible->get_accValue(
childid_self, value.Receive()));
ASSERT_STREQ(L"Value", value);
base::win::ScopedBstr new_value(L"New value");
ScopedBstr new_value(L"New value");
ASSERT_EQ(S_OK, textfield_accessible->put_accValue(childid_self, new_value));
ASSERT_STREQ(L"New value", textfield->text().c_str());
......@@ -81,10 +100,10 @@ TEST_F(NativeViewAcccessibilityWinTest, UnattachedWebView) {
content->AddChildView(web_view);
NativeViewAccessibility::RegisterWebView(web_view);
base::win::ScopedComPtr<IAccessible> web_view_accessible(
ScopedComPtr<IAccessible> web_view_accessible(
web_view->GetNativeViewAccessible());
base::win::ScopedComPtr<IDispatch> result_dispatch;
base::win::ScopedVariant child_index(-999);
ScopedComPtr<IDispatch> result_dispatch;
ScopedVariant child_index(-999);
ASSERT_EQ(E_FAIL, web_view_accessible->get_accChild(
child_index, result_dispatch.Receive()));
......@@ -98,7 +117,7 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(init_params);
base::win::ScopedComPtr<IAccessible> root_view_accessible(
ScopedComPtr<IAccessible> root_view_accessible(
widget.GetRootView()->GetNativeViewAccessible());
LONG child_count = 0;
......@@ -118,5 +137,69 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) {
ASSERT_EQ(2L, child_count);
}
TEST_F(NativeViewAcccessibilityWinTest, RetrieveAllAlerts) {
Widget widget;
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_POPUP);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(init_params);
View* content = new View;
widget.SetContentsView(content);
View* infobar = new View;
content->AddChildView(infobar);
View* infobar2 = new View;
content->AddChildView(infobar2);
View* root_view = content->parent();
ASSERT_EQ(NULL, root_view->parent());
ScopedComPtr<IAccessible2_2> root_view_accessible;
GetIAccessible2InterfaceForView(root_view, root_view_accessible.Receive());
ScopedComPtr<IAccessible2_2> infobar_accessible;
GetIAccessible2InterfaceForView(infobar, infobar_accessible.Receive());
ScopedComPtr<IAccessible2_2> infobar2_accessible;
GetIAccessible2InterfaceForView(infobar2, infobar2_accessible.Receive());
// Initially, there are no alerts
ScopedBstr alerts_bstr(L"alerts");
IUnknown** targets;
long n_targets;
ASSERT_EQ(S_FALSE, root_view_accessible->get_relationTargetsOfType(
alerts_bstr, 0, &targets, &n_targets));
ASSERT_EQ(0, n_targets);
// Fire alert events on the infobars.
infobar->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
infobar2->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
// Now calling get_relationTargetsOfType should retrieve the alerts.
ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
alerts_bstr, 0, &targets, &n_targets));
ASSERT_EQ(2, n_targets);
ASSERT_TRUE(infobar_accessible.IsSameObject(targets[0]));
ASSERT_TRUE(infobar2_accessible.IsSameObject(targets[1]));
CoTaskMemFree(targets);
// If we set max_targets to 1, we should only get the first one.
ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
alerts_bstr, 1, &targets, &n_targets));
ASSERT_EQ(1, n_targets);
ASSERT_TRUE(infobar_accessible.IsSameObject(targets[0]));
CoTaskMemFree(targets);
// If we delete the first view, we should only get the second one now.
delete infobar;
ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
alerts_bstr, 0, &targets, &n_targets));
ASSERT_EQ(1, n_targets);
ASSERT_TRUE(infobar2_accessible.IsSameObject(targets[0]));
CoTaskMemFree(targets);
}
} // namespace test
} // namespace views
......@@ -683,6 +683,9 @@
],
}],
['OS=="win"', {
'dependencies': [
'../../third_party/iaccessible2/iaccessible2.gyp:iaccessible2',
],
'link_settings': {
'libraries': [
'-limm32.lib',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment