1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 #include "base/message_loop/message_loop.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/accessibility/accessibility_extension_api.h"
11 #include "chrome/browser/accessibility/accessibility_extension_api_constants.h"
12 #include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
13 #include "chrome/test/base/testing_profile.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/base/accessibility/accessibility_types.h"
16 #include "ui/base/accessibility/accessible_view_state.h"
17 #include "ui/base/models/simple_menu_model.h"
18 #include "ui/views/controls/button/label_button.h"
19 #include "ui/views/controls/label.h"
20 #include "ui/views/controls/menu/menu_item_view.h"
21 #include "ui/views/controls/menu/menu_runner.h"
22 #include "ui/views/controls/menu/submenu_view.h"
23 #include "ui/views/layout/grid_layout.h"
24 #include "ui/views/test/test_views_delegate.h"
25 #include "ui/views/widget/native_widget.h"
26 #include "ui/views/widget/root_view.h"
27 #include "ui/views/widget/widget.h"
28 #include "ui/views/widget/widget_delegate.h"
31 #include "ui/base/win/scoped_ole_initializer.h"
35 #include "ui/aura/root_window.h"
36 #include "ui/aura/test/aura_test_helper.h"
39 using base::ASCIIToUTF16
;
41 class AccessibilityViewsDelegate
: public views::TestViewsDelegate
{
43 AccessibilityViewsDelegate() {}
44 virtual ~AccessibilityViewsDelegate() {}
46 // Overridden from views::TestViewsDelegate:
47 virtual void NotifyAccessibilityEvent(
48 views::View
* view
, ui::AccessibilityTypes::Event event_type
) OVERRIDE
{
49 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
53 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate
);
56 class AccessibilityWindowDelegate
: public views::WidgetDelegate
{
58 explicit AccessibilityWindowDelegate(views::View
* contents
)
59 : contents_(contents
) { }
61 // Overridden from views::WidgetDelegate:
62 virtual void DeleteDelegate() OVERRIDE
{ delete this; }
63 virtual views::View
* GetContentsView() OVERRIDE
{ return contents_
; }
64 virtual const views::Widget
* GetWidget() const OVERRIDE
{
65 return contents_
->GetWidget();
67 virtual views::Widget
* GetWidget() OVERRIDE
{ return contents_
->GetWidget(); }
70 views::View
* contents_
;
72 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate
);
75 class ViewWithNameAndRole
: public views::View
{
77 explicit ViewWithNameAndRole(const base::string16
& name
,
78 ui::AccessibilityTypes::Role role
)
83 virtual void GetAccessibleState(ui::AccessibleViewState
* state
) OVERRIDE
{
84 views::View::GetAccessibleState(state
);
89 void set_name(const base::string16
& name
) { name_
= name
; }
93 ui::AccessibilityTypes::Role role_
;
94 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole
);
97 class AccessibilityEventRouterViewsTest
98 : public testing::Test
{
100 AccessibilityEventRouterViewsTest() : control_event_count_(0) {
103 virtual void SetUp() {
105 ole_initializer_
.reset(new ui::ScopedOleInitializer());
107 views::ViewsDelegate::views_delegate
= new AccessibilityViewsDelegate();
108 #if defined(USE_AURA)
109 aura_test_helper_
.reset(new aura::test::AuraTestHelper(&message_loop_
));
110 bool allow_test_contexts
= true;
111 aura_test_helper_
->SetUp(allow_test_contexts
);
113 EnableAccessibilityAndListenToFocusNotifications();
116 virtual void TearDown() {
118 #if defined(USE_AURA)
119 aura_test_helper_
->TearDown();
121 delete views::ViewsDelegate::views_delegate
;
122 views::ViewsDelegate::views_delegate
= NULL
;
124 // The Widget's FocusManager is deleted using DeleteSoon - this
125 // forces it to be deleted now, so we don't have any memory leaks
126 // when this method exits.
127 base::MessageLoop::current()->RunUntilIdle();
130 ole_initializer_
.reset();
134 views::Widget
* CreateWindowWithContents(views::View
* contents
) {
135 gfx::NativeView context
= NULL
;
136 #if defined(USE_AURA)
137 context
= aura_test_helper_
->root_window();
139 views::Widget
* widget
= views::Widget::CreateWindowWithContextAndBounds(
140 new AccessibilityWindowDelegate(contents
),
142 gfx::Rect(0, 0, 500, 500));
144 // Create a profile and associate it with this window.
145 widget
->SetNativeWindowProperty(Profile::kProfileKey
, &profile_
);
150 void EnableAccessibilityAndListenToFocusNotifications() {
151 // Switch on accessibility event notifications.
152 ExtensionAccessibilityEventRouter
* accessibility_event_router
=
153 ExtensionAccessibilityEventRouter::GetInstance();
154 accessibility_event_router
->SetAccessibilityEnabled(true);
155 accessibility_event_router
->SetControlEventCallbackForTesting(base::Bind(
156 &AccessibilityEventRouterViewsTest::OnControlEvent
,
157 base::Unretained(this)));
160 void ClearCallback() {
161 ExtensionAccessibilityEventRouter
* accessibility_event_router
=
162 ExtensionAccessibilityEventRouter::GetInstance();
163 accessibility_event_router
->ClearControlEventCallback();
167 // Handle Focus event.
168 virtual void OnControlEvent(ui::AccessibilityTypes::Event event
,
169 const AccessibilityControlInfo
* info
) {
170 control_event_count_
++;
171 last_control_type_
= info
->type();
172 last_control_name_
= info
->name();
173 last_control_context_
= info
->context();
176 base::MessageLoopForUI message_loop_
;
177 int control_event_count_
;
178 std::string last_control_type_
;
179 std::string last_control_name_
;
180 std::string last_control_context_
;
181 TestingProfile profile_
;
183 scoped_ptr
<ui::ScopedOleInitializer
> ole_initializer_
;
185 #if defined(USE_AURA)
186 scoped_ptr
<aura::test::AuraTestHelper
> aura_test_helper_
;
190 TEST_F(AccessibilityEventRouterViewsTest
, TestFocusNotification
) {
191 const char kButton1ASCII
[] = "Button1";
192 const char kButton2ASCII
[] = "Button2";
193 const char kButton3ASCII
[] = "Button3";
194 const char kButton3NewASCII
[] = "Button3New";
196 // Create a contents view with 3 buttons.
197 views::View
* contents
= new views::View();
198 views::LabelButton
* button1
= new views::LabelButton(
199 NULL
, ASCIIToUTF16(kButton1ASCII
));
200 button1
->SetStyle(views::Button::STYLE_BUTTON
);
201 contents
->AddChildView(button1
);
202 views::LabelButton
* button2
= new views::LabelButton(
203 NULL
, ASCIIToUTF16(kButton2ASCII
));
204 button2
->SetStyle(views::Button::STYLE_BUTTON
);
205 contents
->AddChildView(button2
);
206 views::LabelButton
* button3
= new views::LabelButton(
207 NULL
, ASCIIToUTF16(kButton3ASCII
));
208 button3
->SetStyle(views::Button::STYLE_BUTTON
);
209 contents
->AddChildView(button3
);
211 // Put the view in a window.
212 views::Widget
* window
= CreateWindowWithContents(contents
);
215 // Set focus to the first button initially and run message loop to execute
217 button1
->RequestFocus();
218 base::MessageLoop::current()->RunUntilIdle();
220 // Change the accessible name of button3.
221 button3
->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII
));
223 // Advance focus to the next button and test that we got the
224 // expected notification with the name of button 2.
225 views::FocusManager
* focus_manager
= contents
->GetWidget()->GetFocusManager();
226 control_event_count_
= 0;
227 focus_manager
->AdvanceFocus(false);
228 base::MessageLoop::current()->RunUntilIdle();
229 EXPECT_EQ(1, control_event_count_
);
230 EXPECT_EQ(kButton2ASCII
, last_control_name_
);
232 // Advance to button 3. Expect the new accessible name we assigned.
233 focus_manager
->AdvanceFocus(false);
234 base::MessageLoop::current()->RunUntilIdle();
235 EXPECT_EQ(2, control_event_count_
);
236 EXPECT_EQ(kButton3NewASCII
, last_control_name_
);
238 // Advance to button 1 and check the notification.
239 focus_manager
->AdvanceFocus(false);
240 base::MessageLoop::current()->RunUntilIdle();
241 EXPECT_EQ(3, control_event_count_
);
242 EXPECT_EQ(kButton1ASCII
, last_control_name_
);
247 TEST_F(AccessibilityEventRouterViewsTest
, TestToolbarContext
) {
248 const char kToolbarNameASCII
[] = "MyToolbar";
249 const char kButtonNameASCII
[] = "MyButton";
251 // Create a toolbar with a button.
252 views::View
* contents
= new ViewWithNameAndRole(
253 ASCIIToUTF16(kToolbarNameASCII
),
254 ui::AccessibilityTypes::ROLE_TOOLBAR
);
255 views::LabelButton
* button
= new views::LabelButton(
256 NULL
, ASCIIToUTF16(kButtonNameASCII
));
257 button
->SetStyle(views::Button::STYLE_BUTTON
);
258 contents
->AddChildView(button
);
260 // Put the view in a window.
261 views::Widget
* window
= CreateWindowWithContents(contents
);
263 // Set focus to the button.
264 control_event_count_
= 0;
265 button
->RequestFocus();
267 base::MessageLoop::current()->RunUntilIdle();
269 // Test that we got the event with the expected name and context.
270 EXPECT_EQ(1, control_event_count_
);
271 EXPECT_EQ(kButtonNameASCII
, last_control_name_
);
272 EXPECT_EQ(kToolbarNameASCII
, last_control_context_
);
277 TEST_F(AccessibilityEventRouterViewsTest
, TestAlertContext
) {
278 const char kAlertTextASCII
[] = "MyAlertText";
279 const char kButtonNameASCII
[] = "MyButton";
281 // Create an alert with static text and a button, similar to an infobar.
282 views::View
* contents
= new ViewWithNameAndRole(
284 ui::AccessibilityTypes::ROLE_ALERT
);
285 views::Label
* label
= new views::Label(ASCIIToUTF16(kAlertTextASCII
));
286 contents
->AddChildView(label
);
287 views::LabelButton
* button
= new views::LabelButton(
288 NULL
, ASCIIToUTF16(kButtonNameASCII
));
289 button
->SetStyle(views::Button::STYLE_BUTTON
);
290 contents
->AddChildView(button
);
292 // Put the view in a window.
293 views::Widget
* window
= CreateWindowWithContents(contents
);
295 // Set focus to the button.
296 control_event_count_
= 0;
297 button
->RequestFocus();
299 base::MessageLoop::current()->RunUntilIdle();
301 // Test that we got the event with the expected name and context.
302 EXPECT_EQ(1, control_event_count_
);
303 EXPECT_EQ(kButtonNameASCII
, last_control_name_
);
304 EXPECT_EQ(kAlertTextASCII
, last_control_context_
);
309 TEST_F(AccessibilityEventRouterViewsTest
, StateChangeAfterNotification
) {
310 const char kContentsNameASCII
[] = "Contents";
311 const char kOldNameASCII
[] = "OldName";
312 const char kNewNameASCII
[] = "NewName";
314 // Create a toolbar with a button.
315 views::View
* contents
= new ViewWithNameAndRole(
316 ASCIIToUTF16(kContentsNameASCII
),
317 ui::AccessibilityTypes::ROLE_CLIENT
);
318 ViewWithNameAndRole
* child
= new ViewWithNameAndRole(
319 ASCIIToUTF16(kOldNameASCII
),
320 ui::AccessibilityTypes::ROLE_PUSHBUTTON
);
321 child
->SetFocusable(true);
322 contents
->AddChildView(child
);
324 // Put the view in a window.
325 views::Widget
* window
= CreateWindowWithContents(contents
);
327 // Set focus to the child view.
328 control_event_count_
= 0;
329 child
->RequestFocus();
331 // Change the child's name after the focus notification.
332 child
->set_name(ASCIIToUTF16(kNewNameASCII
));
334 // We shouldn't get the notification right away.
335 EXPECT_EQ(0, control_event_count_
);
337 // Process anything in the event loop. Now we should get the notification,
338 // and it should give us the new control name, not the old one.
339 base::MessageLoop::current()->RunUntilIdle();
340 EXPECT_EQ(1, control_event_count_
);
341 EXPECT_EQ(kNewNameASCII
, last_control_name_
);
346 TEST_F(AccessibilityEventRouterViewsTest
, NotificationOnDeletedObject
) {
347 const char kContentsNameASCII
[] = "Contents";
348 const char kNameASCII
[] = "OldName";
350 // Create a toolbar with a button.
351 views::View
* contents
= new ViewWithNameAndRole(
352 ASCIIToUTF16(kContentsNameASCII
),
353 ui::AccessibilityTypes::ROLE_CLIENT
);
354 ViewWithNameAndRole
* child
= new ViewWithNameAndRole(
355 ASCIIToUTF16(kNameASCII
),
356 ui::AccessibilityTypes::ROLE_PUSHBUTTON
);
357 child
->SetFocusable(true);
358 contents
->AddChildView(child
);
360 // Put the view in a window.
361 views::Widget
* window
= CreateWindowWithContents(contents
);
363 // Set focus to the child view.
364 control_event_count_
= 0;
365 child
->RequestFocus();
370 // We shouldn't get the notification right away.
371 EXPECT_EQ(0, control_event_count_
);
373 // Process anything in the event loop. We shouldn't get a notification
374 // because the view is no longer valid, and this shouldn't crash.
375 base::MessageLoop::current()->RunUntilIdle();
376 EXPECT_EQ(0, control_event_count_
);
381 TEST_F(AccessibilityEventRouterViewsTest
, AlertsFromWindowAndControl
) {
382 const char kButtonASCII
[] = "Button";
383 const char* kTypeAlert
= extension_accessibility_api_constants::kTypeAlert
;
384 const char* kTypeWindow
= extension_accessibility_api_constants::kTypeWindow
;
386 // Create a contents view with a button.
387 views::View
* contents
= new views::View();
388 views::LabelButton
* button
= new views::LabelButton(
389 NULL
, ASCIIToUTF16(kButtonASCII
));
390 button
->SetStyle(views::Button::STYLE_BUTTON
);
391 contents
->AddChildView(button
);
393 // Put the view in a window.
394 views::Widget
* window
= CreateWindowWithContents(contents
);
397 // Send an alert event from the button and let the event loop run.
398 control_event_count_
= 0;
399 button
->NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT
, true);
400 base::MessageLoop::current()->RunUntilIdle();
402 EXPECT_EQ(kTypeAlert
, last_control_type_
);
403 EXPECT_EQ(1, control_event_count_
);
404 EXPECT_EQ(kButtonASCII
, last_control_name_
);
406 // Send an alert event from the window and let the event loop run.
407 control_event_count_
= 0;
408 window
->GetRootView()->NotifyAccessibilityEvent(
409 ui::AccessibilityTypes::EVENT_ALERT
, true);
410 base::MessageLoop::current()->RunUntilIdle();
412 EXPECT_EQ(1, control_event_count_
);
413 EXPECT_EQ(kTypeWindow
, last_control_type_
);
420 class SimpleMenuDelegate
: public ui::SimpleMenuModel::Delegate
{
429 SimpleMenuDelegate() {}
430 virtual ~SimpleMenuDelegate() {}
432 views::MenuItemView
* BuildMenu() {
433 menu_model_
.reset(new ui::SimpleMenuModel(this));
434 menu_model_
->AddItem(IDC_MENU_ITEM_1
, ASCIIToUTF16("Item 1"));
435 menu_model_
->AddItem(IDC_MENU_ITEM_2
, ASCIIToUTF16("Item 2"));
436 menu_model_
->AddSeparator(ui::NORMAL_SEPARATOR
);
437 menu_model_
->AddItem(IDC_MENU_INVISIBLE
, ASCIIToUTF16("Invisible"));
438 menu_model_
->AddSeparator(ui::NORMAL_SEPARATOR
);
439 menu_model_
->AddItem(IDC_MENU_ITEM_3
, ASCIIToUTF16("Item 3"));
441 menu_runner_
.reset(new views::MenuRunner(menu_model_
.get()));
442 return menu_runner_
->GetMenu();
445 virtual bool IsCommandIdChecked(int command_id
) const OVERRIDE
{
449 virtual bool IsCommandIdEnabled(int command_id
) const OVERRIDE
{
453 virtual bool IsCommandIdVisible(int command_id
) const OVERRIDE
{
454 return command_id
!= IDC_MENU_INVISIBLE
;
457 virtual bool GetAcceleratorForCommandId(
459 ui::Accelerator
* accelerator
) OVERRIDE
{
463 virtual void ExecuteCommand(int command_id
, int event_flags
) OVERRIDE
{
467 scoped_ptr
<ui::SimpleMenuModel
> menu_model_
;
468 scoped_ptr
<views::MenuRunner
> menu_runner_
;
470 DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate
);
475 TEST_F(AccessibilityEventRouterViewsTest
, MenuIndexAndCountForInvisibleMenu
) {
476 SimpleMenuDelegate menu_delegate
;
477 views::MenuItemView
* menu
= menu_delegate
.BuildMenu();
478 views::View
* menu_container
= menu
->CreateSubmenu();
485 { SimpleMenuDelegate::IDC_MENU_ITEM_1
, 0, 3 },
486 { SimpleMenuDelegate::IDC_MENU_ITEM_2
, 1, 3 },
487 { SimpleMenuDelegate::IDC_MENU_INVISIBLE
, 0, 3 },
488 { SimpleMenuDelegate::IDC_MENU_ITEM_3
, 2, 3 },
491 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(kTestCases
); ++i
) {
495 AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
497 menu
->GetMenuItemByID(kTestCases
[i
].command_id
),
500 EXPECT_EQ(kTestCases
[i
].expected_index
, index
) << "Case " << i
;
501 EXPECT_EQ(kTestCases
[i
].expected_count
, count
) << "Case " << i
;