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"
38 #include "ui/wm/core/default_activation_client.h"
41 using base::ASCIIToUTF16;
43 class AccessibilityViewsDelegate : public views::TestViewsDelegate {
45 AccessibilityViewsDelegate() {}
46 virtual ~AccessibilityViewsDelegate() {}
48 // Overridden from views::TestViewsDelegate:
49 virtual void NotifyAccessibilityEvent(
50 views::View* view, ui::AXEvent event_type) OVERRIDE {
51 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
55 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
58 class AccessibilityWindowDelegate : public views::WidgetDelegate {
60 explicit AccessibilityWindowDelegate(views::View* contents)
61 : contents_(contents) { }
63 // Overridden from views::WidgetDelegate:
64 virtual void DeleteDelegate() OVERRIDE { delete this; }
65 virtual views::View* GetContentsView() OVERRIDE { return contents_; }
66 virtual const views::Widget* GetWidget() const OVERRIDE {
67 return contents_->GetWidget();
69 virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
72 views::View* contents_;
74 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
77 class ViewWithNameAndRole : public views::View {
79 explicit ViewWithNameAndRole(const base::string16& name,
85 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
86 views::View::GetAccessibleState(state);
91 void set_name(const base::string16& name) { name_ = name; }
96 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
99 class AccessibilityEventRouterViewsTest
100 : public testing::Test {
102 AccessibilityEventRouterViewsTest() : control_event_count_(0) {
105 virtual void SetUp() {
107 ole_initializer_.reset(new ui::ScopedOleInitializer());
109 views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
110 #if defined(USE_AURA)
111 // The ContextFactory must exist before any Compositors are created.
112 bool enable_pixel_output = false;
113 ui::InitializeContextFactoryForTests(enable_pixel_output);
115 aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
116 aura_test_helper_->SetUp();
117 new wm::DefaultActivationClient(aura_test_helper_->root_window());
119 EnableAccessibilityAndListenToFocusNotifications();
122 virtual void TearDown() {
124 #if defined(USE_AURA)
125 aura_test_helper_->TearDown();
126 ui::TerminateContextFactoryForTests();
128 delete views::ViewsDelegate::views_delegate;
129 views::ViewsDelegate::views_delegate = NULL;
131 // The Widget's FocusManager is deleted using DeleteSoon - this
132 // forces it to be deleted now, so we don't have any memory leaks
133 // when this method exits.
134 base::MessageLoop::current()->RunUntilIdle();
137 ole_initializer_.reset();
141 views::Widget* CreateWindowWithContents(views::View* contents) {
142 gfx::NativeView context = NULL;
143 #if defined(USE_AURA)
144 context = aura_test_helper_->root_window();
146 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
147 new AccessibilityWindowDelegate(contents),
149 gfx::Rect(0, 0, 500, 500));
151 // Create a profile and associate it with this window.
152 widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
157 void EnableAccessibilityAndListenToFocusNotifications() {
158 // Switch on accessibility event notifications.
159 ExtensionAccessibilityEventRouter* accessibility_event_router =
160 ExtensionAccessibilityEventRouter::GetInstance();
161 accessibility_event_router->SetAccessibilityEnabled(true);
162 accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
163 &AccessibilityEventRouterViewsTest::OnControlEvent,
164 base::Unretained(this)));
167 void ClearCallback() {
168 ExtensionAccessibilityEventRouter* accessibility_event_router =
169 ExtensionAccessibilityEventRouter::GetInstance();
170 accessibility_event_router->ClearControlEventCallback();
174 // Handle Focus event.
175 virtual void OnControlEvent(ui::AXEvent event,
176 const AccessibilityControlInfo* info) {
177 control_event_count_++;
178 last_control_type_ = info->type();
179 last_control_name_ = info->name();
180 last_control_context_ = info->context();
183 base::MessageLoopForUI message_loop_;
184 int control_event_count_;
185 std::string last_control_type_;
186 std::string last_control_name_;
187 std::string last_control_context_;
188 TestingProfile profile_;
190 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
192 #if defined(USE_AURA)
193 scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
197 TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
198 const char kButton1ASCII[] = "Button1";
199 const char kButton2ASCII[] = "Button2";
200 const char kButton3ASCII[] = "Button3";
201 const char kButton3NewASCII[] = "Button3New";
203 // Create a contents view with 3 buttons.
204 views::View* contents = new views::View();
205 views::LabelButton* button1 = new views::LabelButton(
206 NULL, ASCIIToUTF16(kButton1ASCII));
207 button1->SetStyle(views::Button::STYLE_BUTTON);
208 contents->AddChildView(button1);
209 views::LabelButton* button2 = new views::LabelButton(
210 NULL, ASCIIToUTF16(kButton2ASCII));
211 button2->SetStyle(views::Button::STYLE_BUTTON);
212 contents->AddChildView(button2);
213 views::LabelButton* button3 = new views::LabelButton(
214 NULL, ASCIIToUTF16(kButton3ASCII));
215 button3->SetStyle(views::Button::STYLE_BUTTON);
216 contents->AddChildView(button3);
218 // Put the view in a window.
219 views::Widget* window = CreateWindowWithContents(contents);
222 // Set focus to the first button initially and run message loop to execute
224 button1->RequestFocus();
225 base::MessageLoop::current()->RunUntilIdle();
227 // Change the accessible name of button3.
228 button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
230 // Advance focus to the next button and test that we got the
231 // expected notification with the name of button 2.
232 views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
233 control_event_count_ = 0;
234 focus_manager->AdvanceFocus(false);
235 base::MessageLoop::current()->RunUntilIdle();
236 EXPECT_EQ(1, control_event_count_);
237 EXPECT_EQ(kButton2ASCII, last_control_name_);
239 // Advance to button 3. Expect the new accessible name we assigned.
240 focus_manager->AdvanceFocus(false);
241 base::MessageLoop::current()->RunUntilIdle();
242 EXPECT_EQ(2, control_event_count_);
243 EXPECT_EQ(kButton3NewASCII, last_control_name_);
245 // Advance to button 1 and check the notification.
246 focus_manager->AdvanceFocus(false);
247 base::MessageLoop::current()->RunUntilIdle();
248 EXPECT_EQ(3, control_event_count_);
249 EXPECT_EQ(kButton1ASCII, last_control_name_);
254 TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
255 const char kToolbarNameASCII[] = "MyToolbar";
256 const char kButtonNameASCII[] = "MyButton";
258 // Create a toolbar with a button.
259 views::View* contents = new ViewWithNameAndRole(
260 ASCIIToUTF16(kToolbarNameASCII),
261 ui::AX_ROLE_TOOLBAR);
262 views::LabelButton* button = new views::LabelButton(
263 NULL, ASCIIToUTF16(kButtonNameASCII));
264 button->SetStyle(views::Button::STYLE_BUTTON);
265 contents->AddChildView(button);
267 // Put the view in a window.
268 views::Widget* window = CreateWindowWithContents(contents);
270 // Set focus to the button.
271 control_event_count_ = 0;
272 button->RequestFocus();
274 base::MessageLoop::current()->RunUntilIdle();
276 // Test that we got the event with the expected name and context.
277 EXPECT_EQ(1, control_event_count_);
278 EXPECT_EQ(kButtonNameASCII, last_control_name_);
279 EXPECT_EQ(kToolbarNameASCII, last_control_context_);
284 TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
285 const char kAlertTextASCII[] = "MyAlertText";
286 const char kButtonNameASCII[] = "MyButton";
288 // Create an alert with static text and a button, similar to an infobar.
289 views::View* contents = new ViewWithNameAndRole(
292 views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
293 contents->AddChildView(label);
294 views::LabelButton* button = new views::LabelButton(
295 NULL, ASCIIToUTF16(kButtonNameASCII));
296 button->SetStyle(views::Button::STYLE_BUTTON);
297 contents->AddChildView(button);
299 // Put the view in a window.
300 views::Widget* window = CreateWindowWithContents(contents);
302 // Set focus to the button.
303 control_event_count_ = 0;
304 button->RequestFocus();
306 base::MessageLoop::current()->RunUntilIdle();
308 // Test that we got the event with the expected name and context.
309 EXPECT_EQ(1, control_event_count_);
310 EXPECT_EQ(kButtonNameASCII, last_control_name_);
311 EXPECT_EQ(kAlertTextASCII, last_control_context_);
316 TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
317 const char kContentsNameASCII[] = "Contents";
318 const char kOldNameASCII[] = "OldName";
319 const char kNewNameASCII[] = "NewName";
321 // Create a toolbar with a button.
322 views::View* contents = new ViewWithNameAndRole(
323 ASCIIToUTF16(kContentsNameASCII),
325 ViewWithNameAndRole* child = new ViewWithNameAndRole(
326 ASCIIToUTF16(kOldNameASCII),
328 child->SetFocusable(true);
329 contents->AddChildView(child);
331 // Put the view in a window.
332 views::Widget* window = CreateWindowWithContents(contents);
334 // Set focus to the child view.
335 control_event_count_ = 0;
336 child->RequestFocus();
338 // Change the child's name after the focus notification.
339 child->set_name(ASCIIToUTF16(kNewNameASCII));
341 // We shouldn't get the notification right away.
342 EXPECT_EQ(0, control_event_count_);
344 // Process anything in the event loop. Now we should get the notification,
345 // and it should give us the new control name, not the old one.
346 base::MessageLoop::current()->RunUntilIdle();
347 EXPECT_EQ(1, control_event_count_);
348 EXPECT_EQ(kNewNameASCII, last_control_name_);
353 TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
354 const char kContentsNameASCII[] = "Contents";
355 const char kNameASCII[] = "OldName";
357 // Create a toolbar with a button.
358 views::View* contents = new ViewWithNameAndRole(
359 ASCIIToUTF16(kContentsNameASCII),
361 ViewWithNameAndRole* child = new ViewWithNameAndRole(
362 ASCIIToUTF16(kNameASCII),
364 child->SetFocusable(true);
365 contents->AddChildView(child);
367 // Put the view in a window.
368 views::Widget* window = CreateWindowWithContents(contents);
370 // Set focus to the child view.
371 control_event_count_ = 0;
372 child->RequestFocus();
377 // We shouldn't get the notification right away.
378 EXPECT_EQ(0, control_event_count_);
380 // Process anything in the event loop. We shouldn't get a notification
381 // because the view is no longer valid, and this shouldn't crash.
382 base::MessageLoop::current()->RunUntilIdle();
383 EXPECT_EQ(0, control_event_count_);
388 TEST_F(AccessibilityEventRouterViewsTest, AlertsFromWindowAndControl) {
389 const char kButtonASCII[] = "Button";
390 const char* kTypeAlert = extension_accessibility_api_constants::kTypeAlert;
391 const char* kTypeWindow = extension_accessibility_api_constants::kTypeWindow;
393 // Create a contents view with a button.
394 views::View* contents = new views::View();
395 views::LabelButton* button = new views::LabelButton(
396 NULL, ASCIIToUTF16(kButtonASCII));
397 button->SetStyle(views::Button::STYLE_BUTTON);
398 contents->AddChildView(button);
400 // Put the view in a window.
401 views::Widget* window = CreateWindowWithContents(contents);
404 // Send an alert event from the button and let the event loop run.
405 control_event_count_ = 0;
406 button->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
407 base::MessageLoop::current()->RunUntilIdle();
409 EXPECT_EQ(kTypeAlert, last_control_type_);
410 EXPECT_EQ(1, control_event_count_);
411 EXPECT_EQ(kButtonASCII, last_control_name_);
413 // Send an alert event from the window and let the event loop run.
414 control_event_count_ = 0;
415 window->GetRootView()->NotifyAccessibilityEvent(
416 ui::AX_EVENT_ALERT, true);
417 base::MessageLoop::current()->RunUntilIdle();
419 EXPECT_EQ(1, control_event_count_);
420 EXPECT_EQ(kTypeWindow, last_control_type_);
427 class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
436 SimpleMenuDelegate() {}
437 virtual ~SimpleMenuDelegate() {}
439 views::MenuItemView* BuildMenu() {
440 menu_model_.reset(new ui::SimpleMenuModel(this));
441 menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
442 menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
443 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
444 menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
445 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
446 menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));
448 menu_runner_.reset(new views::MenuRunner(menu_model_.get()));
449 return menu_runner_->GetMenu();
452 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
456 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
460 virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
461 return command_id != IDC_MENU_INVISIBLE;
464 virtual bool GetAcceleratorForCommandId(
466 ui::Accelerator* accelerator) OVERRIDE {
470 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
474 scoped_ptr<ui::SimpleMenuModel> menu_model_;
475 scoped_ptr<views::MenuRunner> menu_runner_;
477 DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
482 TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
483 SimpleMenuDelegate menu_delegate;
484 views::MenuItemView* menu = menu_delegate.BuildMenu();
485 views::View* menu_container = menu->CreateSubmenu();
492 { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
493 { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
494 { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
495 { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
498 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
502 AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
504 menu->GetMenuItemByID(kTestCases[i].command_id),
507 EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
508 EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;