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) { ...@@ -189,6 +189,7 @@ void AccessibleWebViewRegistry::QueryIAccessible2Interface(View* web_view) {
long NativeViewAccessibilityWin::next_unique_id_ = 1; long NativeViewAccessibilityWin::next_unique_id_ = 1;
int NativeViewAccessibilityWin::view_storage_ids_[kMaxViewStorageIds] = {0}; int NativeViewAccessibilityWin::view_storage_ids_[kMaxViewStorageIds] = {0};
int NativeViewAccessibilityWin::next_view_storage_id_index_ = 0; int NativeViewAccessibilityWin::next_view_storage_id_index_ = 0;
std::vector<int> NativeViewAccessibilityWin::alert_target_view_storage_ids_;
// static // static
NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { NativeViewAccessibility* NativeViewAccessibility::Create(View* view) {
...@@ -210,6 +211,7 @@ NativeViewAccessibilityWin::NativeViewAccessibilityWin() ...@@ -210,6 +211,7 @@ NativeViewAccessibilityWin::NativeViewAccessibilityWin()
} }
NativeViewAccessibilityWin::~NativeViewAccessibilityWin() { NativeViewAccessibilityWin::~NativeViewAccessibilityWin() {
RemoveAlertTarget();
} }
void NativeViewAccessibilityWin::NotifyAccessibilityEvent( void NativeViewAccessibilityWin::NotifyAccessibilityEvent(
...@@ -236,6 +238,10 @@ void NativeViewAccessibilityWin::NotifyAccessibilityEvent( ...@@ -236,6 +238,10 @@ void NativeViewAccessibilityWin::NotifyAccessibilityEvent(
::NotifyWinEvent(MSAAEvent(event_type), hwnd, OBJID_CLIENT, child_id); ::NotifyWinEvent(MSAAEvent(event_type), hwnd, OBJID_CLIENT, child_id);
next_view_storage_id_index_ = next_view_storage_id_index_ =
(next_view_storage_id_index_ + 1) % kMaxViewStorageIds; (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() { gfx::NativeViewAccessible NativeViewAccessibilityWin::GetNativeObject() {
...@@ -865,6 +871,59 @@ STDMETHODIMP NativeViewAccessibilityWin::get_windowHandle(HWND* window_handle) { ...@@ -865,6 +871,59 @@ STDMETHODIMP NativeViewAccessibilityWin::get_windowHandle(HWND* window_handle) {
return *window_handle ? S_OK : S_FALSE; 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 // IAccessibleText
// //
...@@ -1071,11 +1130,12 @@ STDMETHODIMP NativeViewAccessibilityWin::QueryService( ...@@ -1071,11 +1130,12 @@ STDMETHODIMP NativeViewAccessibilityWin::QueryService(
if (!view_) if (!view_)
return E_FAIL; return E_FAIL;
if (riid == IID_IAccessible2) if (riid == IID_IAccessible2 || riid == IID_IAccessible2_2)
AccessibleWebViewRegistry::GetInstance()->EnableIAccessible2Support(); AccessibleWebViewRegistry::GetInstance()->EnableIAccessible2Support();
if (guidService == IID_IAccessible || if (guidService == IID_IAccessible ||
guidService == IID_IAccessible2 || guidService == IID_IAccessible2 ||
guidService == IID_IAccessible2_2 ||
guidService == IID_IAccessibleText) { guidService == IID_IAccessibleText) {
return QueryInterface(riid, object); return QueryInterface(riid, object);
} }
...@@ -1412,4 +1472,32 @@ void NativeViewAccessibilityWin::PopulateChildWidgetVector( ...@@ -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 } // namespace views
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <UIAutomationCore.h> #include <UIAutomationCore.h>
#include <set> #include <set>
#include <vector>
#include "third_party/iaccessible2/ia2_api_all.h" #include "third_party/iaccessible2/ia2_api_all.h"
#include "ui/accessibility/ax_view_state.h" #include "ui/accessibility/ax_view_state.h"
...@@ -38,7 +39,7 @@ namespace views { ...@@ -38,7 +39,7 @@ namespace views {
class __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) class __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
NativeViewAccessibilityWin NativeViewAccessibilityWin
: public CComObjectRootEx<CComMultiThreadModel>, : public CComObjectRootEx<CComMultiThreadModel>,
public IDispatchImpl<IAccessible2, &IID_IAccessible2, public IDispatchImpl<IAccessible2_2, &IID_IAccessible2_2,
&LIBID_IAccessible2Lib>, &LIBID_IAccessible2Lib>,
public IAccessibleText, public IAccessibleText,
public IServiceProvider, public IServiceProvider,
...@@ -47,9 +48,9 @@ NativeViewAccessibilityWin ...@@ -47,9 +48,9 @@ NativeViewAccessibilityWin
public NativeViewAccessibility { public NativeViewAccessibility {
public: public:
BEGIN_COM_MAP(NativeViewAccessibilityWin) BEGIN_COM_MAP(NativeViewAccessibilityWin)
COM_INTERFACE_ENTRY2(IDispatch, IAccessible2) COM_INTERFACE_ENTRY2(IDispatch, IAccessible2_2)
COM_INTERFACE_ENTRY2(IAccessible, IAccessible2) COM_INTERFACE_ENTRY2(IAccessible, IAccessible2_2)
COM_INTERFACE_ENTRY(IAccessible2) COM_INTERFACE_ENTRY(IAccessible2_2)
COM_INTERFACE_ENTRY(IAccessibleText) COM_INTERFACE_ENTRY(IAccessibleText)
COM_INTERFACE_ENTRY(IServiceProvider) COM_INTERFACE_ENTRY(IServiceProvider)
COM_INTERFACE_ENTRY(IAccessibleEx) COM_INTERFACE_ENTRY(IAccessibleEx)
...@@ -145,6 +146,11 @@ NativeViewAccessibilityWin ...@@ -145,6 +146,11 @@ NativeViewAccessibilityWin
STDMETHODIMP get_windowHandle(HWND* window_handle); STDMETHODIMP get_windowHandle(HWND* window_handle);
STDMETHODIMP get_relationTargetsOfType(BSTR type,
long max_targets,
IUnknown ***targets,
long *n_targets);
// //
// IAccessible2 methods not implemented. // IAccessible2 methods not implemented.
// //
...@@ -152,6 +158,9 @@ NativeViewAccessibilityWin ...@@ -152,6 +158,9 @@ NativeViewAccessibilityWin
STDMETHODIMP get_attributes(BSTR* attributes) { STDMETHODIMP get_attributes(BSTR* attributes) {
return E_NOTIMPL; return E_NOTIMPL;
} }
STDMETHODIMP get_attribute(BSTR name, VARIANT* attribute) {
return E_NOTIMPL;
}
STDMETHODIMP get_indexInParent(LONG* index_in_parent) { STDMETHODIMP get_indexInParent(LONG* index_in_parent) {
return E_NOTIMPL; return E_NOTIMPL;
} }
...@@ -205,6 +214,10 @@ NativeViewAccessibilityWin ...@@ -205,6 +214,10 @@ NativeViewAccessibilityWin
STDMETHODIMP get_locale(IA2Locale* locale) { STDMETHODIMP get_locale(IA2Locale* locale) {
return E_NOTIMPL; return E_NOTIMPL;
} }
STDMETHODIMP get_accessibleWithCaret(IUnknown** accessible,
long* caret_offset) {
return E_NOTIMPL;
}
// //
// IAccessibleText methods. // IAccessibleText methods.
...@@ -396,6 +409,12 @@ NativeViewAccessibilityWin ...@@ -396,6 +409,12 @@ NativeViewAccessibilityWin
// NativeViewHost. // NativeViewHost.
void PopulateChildWidgetVector(std::vector<Widget*>* child_widgets); 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. // Give CComObject access to the class constructor.
template <class Base> friend class CComObject; template <class Base> friend class CComObject;
...@@ -418,6 +437,11 @@ NativeViewAccessibilityWin ...@@ -418,6 +437,11 @@ NativeViewAccessibilityWin
// Next index into |view_storage_ids_| to use. // Next index into |view_storage_ids_| to use.
static int next_view_storage_id_index_; 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); DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin);
}; };
......
...@@ -7,14 +7,33 @@ ...@@ -7,14 +7,33 @@
#include "base/win/scoped_bstr.h" #include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h" #include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.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/accessibility/native_view_accessibility.h"
#include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_base.h" #include "ui/views/test/views_test_base.h"
using base::win::ScopedBstr;
using base::win::ScopedComPtr;
using base::win::ScopedVariant;
namespace views { namespace views {
namespace test { 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) { TEST_F(NativeViewAcccessibilityWinTest, TextfieldAccessibility) {
Widget widget; Widget widget;
...@@ -31,32 +50,32 @@ TEST_F(NativeViewAcccessibilityWinTest, TextfieldAccessibility) { ...@@ -31,32 +50,32 @@ TEST_F(NativeViewAcccessibilityWinTest, TextfieldAccessibility) {
textfield->SetText(L"Value"); textfield->SetText(L"Value");
content->AddChildView(textfield); content->AddChildView(textfield);
base::win::ScopedComPtr<IAccessible> content_accessible( ScopedComPtr<IAccessible> content_accessible(
content->GetNativeViewAccessible()); content->GetNativeViewAccessible());
LONG child_count = 0; LONG child_count = 0;
ASSERT_EQ(S_OK, content_accessible->get_accChildCount(&child_count)); ASSERT_EQ(S_OK, content_accessible->get_accChildCount(&child_count));
ASSERT_EQ(1L, child_count); ASSERT_EQ(1L, child_count);
base::win::ScopedComPtr<IDispatch> textfield_dispatch; ScopedComPtr<IDispatch> textfield_dispatch;
base::win::ScopedComPtr<IAccessible> textfield_accessible; ScopedComPtr<IAccessible> textfield_accessible;
base::win::ScopedVariant child_index(1); ScopedVariant child_index(1);
ASSERT_EQ(S_OK, content_accessible->get_accChild( ASSERT_EQ(S_OK, content_accessible->get_accChild(
child_index, textfield_dispatch.Receive())); child_index, textfield_dispatch.Receive()));
ASSERT_EQ(S_OK, textfield_dispatch.QueryInterface( ASSERT_EQ(S_OK, textfield_dispatch.QueryInterface(
textfield_accessible.Receive())); textfield_accessible.Receive()));
base::win::ScopedBstr name; ScopedBstr name;
base::win::ScopedVariant childid_self(CHILDID_SELF); ScopedVariant childid_self(CHILDID_SELF);
ASSERT_EQ(S_OK, textfield_accessible->get_accName( ASSERT_EQ(S_OK, textfield_accessible->get_accName(
childid_self, name.Receive())); childid_self, name.Receive()));
ASSERT_STREQ(L"Name", name); ASSERT_STREQ(L"Name", name);
base::win::ScopedBstr value; ScopedBstr value;
ASSERT_EQ(S_OK, textfield_accessible->get_accValue( ASSERT_EQ(S_OK, textfield_accessible->get_accValue(
childid_self, value.Receive())); childid_self, value.Receive()));
ASSERT_STREQ(L"Value", value); 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_EQ(S_OK, textfield_accessible->put_accValue(childid_self, new_value));
ASSERT_STREQ(L"New value", textfield->text().c_str()); ASSERT_STREQ(L"New value", textfield->text().c_str());
...@@ -81,10 +100,10 @@ TEST_F(NativeViewAcccessibilityWinTest, UnattachedWebView) { ...@@ -81,10 +100,10 @@ TEST_F(NativeViewAcccessibilityWinTest, UnattachedWebView) {
content->AddChildView(web_view); content->AddChildView(web_view);
NativeViewAccessibility::RegisterWebView(web_view); NativeViewAccessibility::RegisterWebView(web_view);
base::win::ScopedComPtr<IAccessible> web_view_accessible( ScopedComPtr<IAccessible> web_view_accessible(
web_view->GetNativeViewAccessible()); web_view->GetNativeViewAccessible());
base::win::ScopedComPtr<IDispatch> result_dispatch; ScopedComPtr<IDispatch> result_dispatch;
base::win::ScopedVariant child_index(-999); ScopedVariant child_index(-999);
ASSERT_EQ(E_FAIL, web_view_accessible->get_accChild( ASSERT_EQ(E_FAIL, web_view_accessible->get_accChild(
child_index, result_dispatch.Receive())); child_index, result_dispatch.Receive()));
...@@ -98,7 +117,7 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) { ...@@ -98,7 +117,7 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(init_params); widget.Init(init_params);
base::win::ScopedComPtr<IAccessible> root_view_accessible( ScopedComPtr<IAccessible> root_view_accessible(
widget.GetRootView()->GetNativeViewAccessible()); widget.GetRootView()->GetNativeViewAccessible());
LONG child_count = 0; LONG child_count = 0;
...@@ -118,5 +137,69 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) { ...@@ -118,5 +137,69 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) {
ASSERT_EQ(2L, child_count); 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 test
} // namespace views } // namespace views
...@@ -683,6 +683,9 @@ ...@@ -683,6 +683,9 @@
], ],
}], }],
['OS=="win"', { ['OS=="win"', {
'dependencies': [
'../../third_party/iaccessible2/iaccessible2.gyp:iaccessible2',
],
'link_settings': { 'link_settings': {
'libraries': [ 'libraries': [
'-limm32.lib', '-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