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/accessibility/ax_enums.h"
16 #include "ui/accessibility/ax_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/test/aura_test_helper.h"
36 #include "ui/aura/window_event_dispatcher.h"
37 #include "ui/compositor/test/context_factories_for_test.h"
40 using base::ASCIIToUTF16;
42 class AccessibilityViewsDelegate : public views::TestViewsDelegate {
44 AccessibilityViewsDelegate() {}
45 virtual ~AccessibilityViewsDelegate() {}
47 // Overridden from views::TestViewsDelegate:
48 virtual void NotifyAccessibilityEvent(
49 views::View* view, ui::AXEvent event_type) OVERRIDE {
50 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
54 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
57 class AccessibilityWindowDelegate : public views::WidgetDelegate {
59 explicit AccessibilityWindowDelegate(views::View* contents)
60 : contents_(contents) { }
62 // Overridden from views::WidgetDelegate:
63 virtual void DeleteDelegate() OVERRIDE { delete this; }
64 virtual views::View* GetContentsView() OVERRIDE { return contents_; }
65 virtual const views::Widget* GetWidget() const OVERRIDE {
66 return contents_->GetWidget();
68 virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
71 views::View* contents_;
73 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
76 class ViewWithNameAndRole : public views::View {
78 explicit ViewWithNameAndRole(const base::string16& name,
84 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
85 views::View::GetAccessibleState(state);
90 void set_name(const base::string16& name) { name_ = name; }
95 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
98 class AccessibilityEventRouterViewsTest
99 : public testing::Test {
101 AccessibilityEventRouterViewsTest() : control_event_count_(0) {
104 virtual void SetUp() {
106 ole_initializer_.reset(new ui::ScopedOleInitializer());
108 views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
109 #if defined(USE_AURA)
110 // The ContextFactory must exist before any Compositors are created.
111 bool enable_pixel_output = false;
112 ui::InitializeContextFactoryForTests(enable_pixel_output);
114 aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
115 aura_test_helper_->SetUp();
117 EnableAccessibilityAndListenToFocusNotifications();
120 virtual void TearDown() {
122 #if defined(USE_AURA)
123 aura_test_helper_->TearDown();
124 ui::TerminateContextFactoryForTests();
126 delete views::ViewsDelegate::views_delegate;
127 views::ViewsDelegate::views_delegate = NULL;
129 // The Widget's FocusManager is deleted using DeleteSoon - this
130 // forces it to be deleted now, so we don't have any memory leaks
131 // when this method exits.
132 base::MessageLoop::current()->RunUntilIdle();
135 ole_initializer_.reset();
139 views::Widget* CreateWindowWithContents(views::View* contents) {
140 gfx::NativeView context = NULL;
141 #if defined(USE_AURA)
142 context = aura_test_helper_->root_window();
144 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
145 new AccessibilityWindowDelegate(contents),
147 gfx::Rect(0, 0, 500, 500));
149 // Create a profile and associate it with this window.
150 widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
155 void EnableAccessibilityAndListenToFocusNotifications() {
156 // Switch on accessibility event notifications.
157 ExtensionAccessibilityEventRouter* accessibility_event_router =
158 ExtensionAccessibilityEventRouter::GetInstance();
159 accessibility_event_router->SetAccessibilityEnabled(true);
160 accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
161 &AccessibilityEventRouterViewsTest::OnControlEvent,
162 base::Unretained(this)));
165 void ClearCallback() {
166 ExtensionAccessibilityEventRouter* accessibility_event_router =
167 ExtensionAccessibilityEventRouter::GetInstance();
168 accessibility_event_router->ClearControlEventCallback();
172 // Handle Focus event.
173 virtual void OnControlEvent(ui::AXEvent event,
174 const AccessibilityControlInfo* info) {
175 control_event_count_++;
176 last_control_type_ = info->type();
177 last_control_name_ = info->name();
178 last_control_context_ = info->context();
181 base::MessageLoopForUI message_loop_;
182 int control_event_count_;
183 std::string last_control_type_;
184 std::string last_control_name_;
185 std::string last_control_context_;
186 TestingProfile profile_;
188 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
190 #if defined(USE_AURA)
191 scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
195 TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
196 const char kButton1ASCII[] = "Button1";
197 const char kButton2ASCII[] = "Button2";
198 const char kButton3ASCII[] = "Button3";
199 const char kButton3NewASCII[] = "Button3New";
201 // Create a contents view with 3 buttons.
202 views::View* contents = new views::View();
203 views::LabelButton* button1 = new views::LabelButton(
204 NULL, ASCIIToUTF16(kButton1ASCII));
205 button1->SetStyle(views::Button::STYLE_BUTTON);
206 contents->AddChildView(button1);
207 views::LabelButton* button2 = new views::LabelButton(
208 NULL, ASCIIToUTF16(kButton2ASCII));
209 button2->SetStyle(views::Button::STYLE_BUTTON);
210 contents->AddChildView(button2);
211 views::LabelButton* button3 = new views::LabelButton(
212 NULL, ASCIIToUTF16(kButton3ASCII));
213 button3->SetStyle(views::Button::STYLE_BUTTON);
214 contents->AddChildView(button3);
216 // Put the view in a window.
217 views::Widget* window = CreateWindowWithContents(contents);
220 // Set focus to the first button initially and run message loop to execute
222 button1->RequestFocus();
223 base::MessageLoop::current()->RunUntilIdle();
225 // Change the accessible name of button3.
226 button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
228 // Advance focus to the next button and test that we got the
229 // expected notification with the name of button 2.
230 views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
231 control_event_count_ = 0;
232 focus_manager->AdvanceFocus(false);
233 base::MessageLoop::current()->RunUntilIdle();
234 EXPECT_EQ(1, control_event_count_);
235 EXPECT_EQ(kButton2ASCII, last_control_name_);
237 // Advance to button 3. Expect the new accessible name we assigned.
238 focus_manager->AdvanceFocus(false);
239 base::MessageLoop::current()->RunUntilIdle();
240 EXPECT_EQ(2, control_event_count_);
241 EXPECT_EQ(kButton3NewASCII, last_control_name_);
243 // Advance to button 1 and check the notification.
244 focus_manager->AdvanceFocus(false);
245 base::MessageLoop::current()->RunUntilIdle();
246 EXPECT_EQ(3, control_event_count_);
247 EXPECT_EQ(kButton1ASCII, last_control_name_);
252 TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
253 const char kToolbarNameASCII[] = "MyToolbar";
254 const char kButtonNameASCII[] = "MyButton";
256 // Create a toolbar with a button.
257 views::View* contents = new ViewWithNameAndRole(
258 ASCIIToUTF16(kToolbarNameASCII),
259 ui::AX_ROLE_TOOLBAR);
260 views::LabelButton* button = new views::LabelButton(
261 NULL, ASCIIToUTF16(kButtonNameASCII));
262 button->SetStyle(views::Button::STYLE_BUTTON);
263 contents->AddChildView(button);
265 // Put the view in a window.
266 views::Widget* window = CreateWindowWithContents(contents);
268 // Set focus to the button.
269 control_event_count_ = 0;
270 button->RequestFocus();
272 base::MessageLoop::current()->RunUntilIdle();
274 // Test that we got the event with the expected name and context.
275 EXPECT_EQ(1, control_event_count_);
276 EXPECT_EQ(kButtonNameASCII, last_control_name_);
277 EXPECT_EQ(kToolbarNameASCII, last_control_context_);
282 TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
283 const char kAlertTextASCII[] = "MyAlertText";
284 const char kButtonNameASCII[] = "MyButton";
286 // Create an alert with static text and a button, similar to an infobar.
287 views::View* contents = new ViewWithNameAndRole(
290 views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
291 contents->AddChildView(label);
292 views::LabelButton* button = new views::LabelButton(
293 NULL, ASCIIToUTF16(kButtonNameASCII));
294 button->SetStyle(views::Button::STYLE_BUTTON);
295 contents->AddChildView(button);
297 // Put the view in a window.
298 views::Widget* window = CreateWindowWithContents(contents);
300 // Set focus to the button.
301 control_event_count_ = 0;
302 button->RequestFocus();
304 base::MessageLoop::current()->RunUntilIdle();
306 // Test that we got the event with the expected name and context.
307 EXPECT_EQ(1, control_event_count_);
308 EXPECT_EQ(kButtonNameASCII, last_control_name_);
309 EXPECT_EQ(kAlertTextASCII, last_control_context_);
314 TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
315 const char kContentsNameASCII[] = "Contents";
316 const char kOldNameASCII[] = "OldName";
317 const char kNewNameASCII[] = "NewName";
319 // Create a toolbar with a button.
320 views::View* contents = new ViewWithNameAndRole(
321 ASCIIToUTF16(kContentsNameASCII),
323 ViewWithNameAndRole* child = new ViewWithNameAndRole(
324 ASCIIToUTF16(kOldNameASCII),
326 child->SetFocusable(true);
327 contents->AddChildView(child);
329 // Put the view in a window.
330 views::Widget* window = CreateWindowWithContents(contents);
332 // Set focus to the child view.
333 control_event_count_ = 0;
334 child->RequestFocus();
336 // Change the child's name after the focus notification.
337 child->set_name(ASCIIToUTF16(kNewNameASCII));
339 // We shouldn't get the notification right away.
340 EXPECT_EQ(0, control_event_count_);
342 // Process anything in the event loop. Now we should get the notification,
343 // and it should give us the new control name, not the old one.
344 base::MessageLoop::current()->RunUntilIdle();
345 EXPECT_EQ(1, control_event_count_);
346 EXPECT_EQ(kNewNameASCII, last_control_name_);
351 TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
352 const char kContentsNameASCII[] = "Contents";
353 const char kNameASCII[] = "OldName";
355 // Create a toolbar with a button.
356 views::View* contents = new ViewWithNameAndRole(
357 ASCIIToUTF16(kContentsNameASCII),
359 ViewWithNameAndRole* child = new ViewWithNameAndRole(
360 ASCIIToUTF16(kNameASCII),
362 child->SetFocusable(true);
363 contents->AddChildView(child);
365 // Put the view in a window.
366 views::Widget* window = CreateWindowWithContents(contents);
368 // Set focus to the child view.
369 control_event_count_ = 0;
370 child->RequestFocus();
375 // We shouldn't get the notification right away.
376 EXPECT_EQ(0, control_event_count_);
378 // Process anything in the event loop. We shouldn't get a notification
379 // because the view is no longer valid, and this shouldn't crash.
380 base::MessageLoop::current()->RunUntilIdle();
381 EXPECT_EQ(0, control_event_count_);
386 TEST_F(AccessibilityEventRouterViewsTest, AlertsFromWindowAndControl) {
387 const char kButtonASCII[] = "Button";
388 const char* kTypeAlert = extension_accessibility_api_constants::kTypeAlert;
389 const char* kTypeWindow = extension_accessibility_api_constants::kTypeWindow;
391 // Create a contents view with a button.
392 views::View* contents = new views::View();
393 views::LabelButton* button = new views::LabelButton(
394 NULL, ASCIIToUTF16(kButtonASCII));
395 button->SetStyle(views::Button::STYLE_BUTTON);
396 contents->AddChildView(button);
398 // Put the view in a window.
399 views::Widget* window = CreateWindowWithContents(contents);
402 // Send an alert event from the button and let the event loop run.
403 control_event_count_ = 0;
404 button->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
405 base::MessageLoop::current()->RunUntilIdle();
407 EXPECT_EQ(kTypeAlert, last_control_type_);
408 EXPECT_EQ(1, control_event_count_);
409 EXPECT_EQ(kButtonASCII, last_control_name_);
411 // Send an alert event from the window and let the event loop run.
412 control_event_count_ = 0;
413 window->GetRootView()->NotifyAccessibilityEvent(
414 ui::AX_EVENT_ALERT, true);
415 base::MessageLoop::current()->RunUntilIdle();
417 EXPECT_EQ(1, control_event_count_);
418 EXPECT_EQ(kTypeWindow, last_control_type_);
425 class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
434 SimpleMenuDelegate() {}
435 virtual ~SimpleMenuDelegate() {}
437 views::MenuItemView* BuildMenu() {
438 menu_model_.reset(new ui::SimpleMenuModel(this));
439 menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
440 menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
441 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
442 menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
443 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
444 menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));
446 menu_runner_.reset(new views::MenuRunner(menu_model_.get()));
447 return menu_runner_->GetMenu();
450 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
454 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
458 virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
459 return command_id != IDC_MENU_INVISIBLE;
462 virtual bool GetAcceleratorForCommandId(
464 ui::Accelerator* accelerator) OVERRIDE {
468 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
472 scoped_ptr<ui::SimpleMenuModel> menu_model_;
473 scoped_ptr<views::MenuRunner> menu_runner_;
475 DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
480 TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
481 SimpleMenuDelegate menu_delegate;
482 views::MenuItemView* menu = menu_delegate.BuildMenu();
483 views::View* menu_container = menu->CreateSubmenu();
490 { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
491 { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
492 { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
493 { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
496 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
500 AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
502 menu->GetMenuItemByID(kTestCases[i].command_id),
505 EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
506 EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;