accessibility_event_router_views_unittest.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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. 4 5#include <string> 6 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_model_adapter.h" 22#include "ui/views/controls/menu/menu_runner.h" 23#include "ui/views/controls/menu/submenu_view.h" 24#include "ui/views/layout/grid_layout.h" 25#include "ui/views/test/test_views_delegate.h" 26#include "ui/views/widget/native_widget.h" 27#include "ui/views/widget/root_view.h" 28#include "ui/views/widget/widget.h" 29#include "ui/views/widget/widget_delegate.h" 30 31#if defined(OS_WIN) 32#include "ui/base/win/scoped_ole_initializer.h" 33#endif 34 35#if defined(USE_AURA) 36#include "ui/aura/test/aura_test_helper.h" 37#include "ui/aura/window_event_dispatcher.h" 38#include "ui/compositor/test/context_factories_for_test.h" 39#include "ui/wm/core/default_activation_client.h" 40#endif 41 42using base::ASCIIToUTF16; 43 44class AccessibilityViewsDelegate : public views::TestViewsDelegate { 45 public: 46 AccessibilityViewsDelegate() {} 47 virtual ~AccessibilityViewsDelegate() {} 48 49 // Overridden from views::TestViewsDelegate: 50 virtual void NotifyAccessibilityEvent( 51 views::View* view, ui::AXEvent event_type) OVERRIDE { 52 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent( 53 view, event_type); 54 } 55 56 private: 57 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate); 58}; 59 60class AccessibilityWindowDelegate : public views::WidgetDelegate { 61 public: 62 explicit AccessibilityWindowDelegate(views::View* contents) 63 : contents_(contents) { } 64 65 // Overridden from views::WidgetDelegate: 66 virtual void DeleteDelegate() OVERRIDE { delete this; } 67 virtual views::View* GetContentsView() OVERRIDE { return contents_; } 68 virtual const views::Widget* GetWidget() const OVERRIDE { 69 return contents_->GetWidget(); 70 } 71 virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); } 72 73 private: 74 views::View* contents_; 75 76 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate); 77}; 78 79class ViewWithNameAndRole : public views::View { 80 public: 81 explicit ViewWithNameAndRole(const base::string16& name, 82 ui::AXRole role) 83 : name_(name), 84 role_(role) { 85 } 86 87 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE { 88 views::View::GetAccessibleState(state); 89 state->name = name_; 90 state->role = role_; 91 } 92 93 void set_name(const base::string16& name) { name_ = name; } 94 95 private: 96 base::string16 name_; 97 ui::AXRole role_; 98 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole); 99}; 100 101class AccessibilityEventRouterViewsTest 102 : public testing::Test { 103 public: 104 AccessibilityEventRouterViewsTest() : control_event_count_(0) { 105 } 106 107 virtual void SetUp() { 108#if defined(OS_WIN) 109 ole_initializer_.reset(new ui::ScopedOleInitializer()); 110#endif 111 views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate(); 112#if defined(USE_AURA) 113 // The ContextFactory must exist before any Compositors are created. 114 bool enable_pixel_output = false; 115 ui::ContextFactory* context_factory = 116 ui::InitializeContextFactoryForTests(enable_pixel_output); 117 118 aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_)); 119 aura_test_helper_->SetUp(context_factory); 120 new wm::DefaultActivationClient(aura_test_helper_->root_window()); 121#endif // USE_AURA 122 EnableAccessibilityAndListenToFocusNotifications(); 123 } 124 125 virtual void TearDown() { 126 ClearCallback(); 127#if defined(USE_AURA) 128 aura_test_helper_->TearDown(); 129 ui::TerminateContextFactoryForTests(); 130#endif 131 delete views::ViewsDelegate::views_delegate; 132 133 // The Widget's FocusManager is deleted using DeleteSoon - this 134 // forces it to be deleted now, so we don't have any memory leaks 135 // when this method exits. 136 base::MessageLoop::current()->RunUntilIdle(); 137 138#if defined(OS_WIN) 139 ole_initializer_.reset(); 140#endif 141 } 142 143 views::Widget* CreateWindowWithContents(views::View* contents) { 144 gfx::NativeWindow context = NULL; 145#if defined(USE_AURA) 146 context = aura_test_helper_->root_window(); 147#endif 148 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds( 149 new AccessibilityWindowDelegate(contents), 150 context, 151 gfx::Rect(0, 0, 500, 500)); 152 153 // Create a profile and associate it with this window. 154 widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_); 155 156 return widget; 157 } 158 159 void EnableAccessibilityAndListenToFocusNotifications() { 160 // Switch on accessibility event notifications. 161 ExtensionAccessibilityEventRouter* accessibility_event_router = 162 ExtensionAccessibilityEventRouter::GetInstance(); 163 accessibility_event_router->SetAccessibilityEnabled(true); 164 accessibility_event_router->SetControlEventCallbackForTesting(base::Bind( 165 &AccessibilityEventRouterViewsTest::OnControlEvent, 166 base::Unretained(this))); 167 } 168 169 void ClearCallback() { 170 ExtensionAccessibilityEventRouter* accessibility_event_router = 171 ExtensionAccessibilityEventRouter::GetInstance(); 172 accessibility_event_router->ClearControlEventCallback(); 173 } 174 175 protected: 176 // Handle Focus event. 177 virtual void OnControlEvent(ui::AXEvent event, 178 const AccessibilityControlInfo* info) { 179 control_event_count_++; 180 last_control_type_ = info->type(); 181 last_control_name_ = info->name(); 182 last_control_context_ = info->context(); 183 } 184 185 base::MessageLoopForUI message_loop_; 186 int control_event_count_; 187 std::string last_control_type_; 188 std::string last_control_name_; 189 std::string last_control_context_; 190 TestingProfile profile_; 191#if defined(OS_WIN) 192 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_; 193#endif 194#if defined(USE_AURA) 195 scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_; 196#endif 197}; 198 199TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) { 200 const char kButton1ASCII[] = "Button1"; 201 const char kButton2ASCII[] = "Button2"; 202 const char kButton3ASCII[] = "Button3"; 203 const char kButton3NewASCII[] = "Button3New"; 204 205 // Create a contents view with 3 buttons. 206 views::View* contents = new views::View(); 207 views::LabelButton* button1 = new views::LabelButton( 208 NULL, ASCIIToUTF16(kButton1ASCII)); 209 button1->SetStyle(views::Button::STYLE_BUTTON); 210 contents->AddChildView(button1); 211 views::LabelButton* button2 = new views::LabelButton( 212 NULL, ASCIIToUTF16(kButton2ASCII)); 213 button2->SetStyle(views::Button::STYLE_BUTTON); 214 contents->AddChildView(button2); 215 views::LabelButton* button3 = new views::LabelButton( 216 NULL, ASCIIToUTF16(kButton3ASCII)); 217 button3->SetStyle(views::Button::STYLE_BUTTON); 218 contents->AddChildView(button3); 219 220 // Put the view in a window. 221 views::Widget* window = CreateWindowWithContents(contents); 222 window->Show(); 223 224 // Set focus to the first button initially and run message loop to execute 225 // callback. 226 button1->RequestFocus(); 227 base::MessageLoop::current()->RunUntilIdle(); 228 229 // Change the accessible name of button3. 230 button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII)); 231 232 // Advance focus to the next button and test that we got the 233 // expected notification with the name of button 2. 234 views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager(); 235 control_event_count_ = 0; 236 focus_manager->AdvanceFocus(false); 237 base::MessageLoop::current()->RunUntilIdle(); 238 EXPECT_EQ(1, control_event_count_); 239 EXPECT_EQ(kButton2ASCII, last_control_name_); 240 241 // Advance to button 3. Expect the new accessible name we assigned. 242 focus_manager->AdvanceFocus(false); 243 base::MessageLoop::current()->RunUntilIdle(); 244 EXPECT_EQ(2, control_event_count_); 245 EXPECT_EQ(kButton3NewASCII, last_control_name_); 246 247 // Advance to button 1 and check the notification. 248 focus_manager->AdvanceFocus(false); 249 base::MessageLoop::current()->RunUntilIdle(); 250 EXPECT_EQ(3, control_event_count_); 251 EXPECT_EQ(kButton1ASCII, last_control_name_); 252 253 window->CloseNow(); 254} 255 256TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) { 257 const char kToolbarNameASCII[] = "MyToolbar"; 258 const char kButtonNameASCII[] = "MyButton"; 259 260 // Create a toolbar with a button. 261 views::View* contents = new ViewWithNameAndRole( 262 ASCIIToUTF16(kToolbarNameASCII), 263 ui::AX_ROLE_TOOLBAR); 264 views::LabelButton* button = new views::LabelButton( 265 NULL, ASCIIToUTF16(kButtonNameASCII)); 266 button->SetStyle(views::Button::STYLE_BUTTON); 267 contents->AddChildView(button); 268 269 // Put the view in a window. 270 views::Widget* window = CreateWindowWithContents(contents); 271 272 // Set focus to the button. 273 control_event_count_ = 0; 274 button->RequestFocus(); 275 276 base::MessageLoop::current()->RunUntilIdle(); 277 278 // Test that we got the event with the expected name and context. 279 EXPECT_EQ(1, control_event_count_); 280 EXPECT_EQ(kButtonNameASCII, last_control_name_); 281 EXPECT_EQ(kToolbarNameASCII, last_control_context_); 282 283 window->CloseNow(); 284} 285 286TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) { 287 const char kAlertTextASCII[] = "MyAlertText"; 288 const char kButtonNameASCII[] = "MyButton"; 289 290 // Create an alert with static text and a button, similar to an infobar. 291 views::View* contents = new ViewWithNameAndRole( 292 base::string16(), 293 ui::AX_ROLE_ALERT); 294 views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII)); 295 contents->AddChildView(label); 296 views::LabelButton* button = new views::LabelButton( 297 NULL, ASCIIToUTF16(kButtonNameASCII)); 298 button->SetStyle(views::Button::STYLE_BUTTON); 299 contents->AddChildView(button); 300 301 // Put the view in a window. 302 views::Widget* window = CreateWindowWithContents(contents); 303 304 // Set focus to the button. 305 control_event_count_ = 0; 306 button->RequestFocus(); 307 308 base::MessageLoop::current()->RunUntilIdle(); 309 310 // Test that we got the event with the expected name and context. 311 EXPECT_EQ(1, control_event_count_); 312 EXPECT_EQ(kButtonNameASCII, last_control_name_); 313 EXPECT_EQ(kAlertTextASCII, last_control_context_); 314 315 window->CloseNow(); 316} 317 318TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) { 319 const char kContentsNameASCII[] = "Contents"; 320 const char kOldNameASCII[] = "OldName"; 321 const char kNewNameASCII[] = "NewName"; 322 323 // Create a toolbar with a button. 324 views::View* contents = new ViewWithNameAndRole( 325 ASCIIToUTF16(kContentsNameASCII), 326 ui::AX_ROLE_CLIENT); 327 ViewWithNameAndRole* child = new ViewWithNameAndRole( 328 ASCIIToUTF16(kOldNameASCII), 329 ui::AX_ROLE_BUTTON); 330 child->SetFocusable(true); 331 contents->AddChildView(child); 332 333 // Put the view in a window. 334 views::Widget* window = CreateWindowWithContents(contents); 335 336 // Set focus to the child view. 337 control_event_count_ = 0; 338 child->RequestFocus(); 339 340 // Change the child's name after the focus notification. 341 child->set_name(ASCIIToUTF16(kNewNameASCII)); 342 343 // We shouldn't get the notification right away. 344 EXPECT_EQ(0, control_event_count_); 345 346 // Process anything in the event loop. Now we should get the notification, 347 // and it should give us the new control name, not the old one. 348 base::MessageLoop::current()->RunUntilIdle(); 349 EXPECT_EQ(1, control_event_count_); 350 EXPECT_EQ(kNewNameASCII, last_control_name_); 351 352 window->CloseNow(); 353} 354 355TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) { 356 const char kContentsNameASCII[] = "Contents"; 357 const char kNameASCII[] = "OldName"; 358 359 // Create a toolbar with a button. 360 views::View* contents = new ViewWithNameAndRole( 361 ASCIIToUTF16(kContentsNameASCII), 362 ui::AX_ROLE_CLIENT); 363 ViewWithNameAndRole* child = new ViewWithNameAndRole( 364 ASCIIToUTF16(kNameASCII), 365 ui::AX_ROLE_BUTTON); 366 child->SetFocusable(true); 367 contents->AddChildView(child); 368 369 // Put the view in a window. 370 views::Widget* window = CreateWindowWithContents(contents); 371 372 // Set focus to the child view. 373 control_event_count_ = 0; 374 child->RequestFocus(); 375 376 // Delete the child! 377 delete child; 378 379 // We shouldn't get the notification right away. 380 EXPECT_EQ(0, control_event_count_); 381 382 // Process anything in the event loop. We shouldn't get a notification 383 // because the view is no longer valid, and this shouldn't crash. 384 base::MessageLoop::current()->RunUntilIdle(); 385 EXPECT_EQ(0, control_event_count_); 386 387 window->CloseNow(); 388} 389 390TEST_F(AccessibilityEventRouterViewsTest, AlertsFromWindowAndControl) { 391 const char kButtonASCII[] = "Button"; 392 const char* kTypeAlert = extension_accessibility_api_constants::kTypeAlert; 393 const char* kTypeWindow = extension_accessibility_api_constants::kTypeWindow; 394 395 // Create a contents view with a button. 396 views::View* contents = new views::View(); 397 views::LabelButton* button = new views::LabelButton( 398 NULL, ASCIIToUTF16(kButtonASCII)); 399 button->SetStyle(views::Button::STYLE_BUTTON); 400 contents->AddChildView(button); 401 402 // Put the view in a window. 403 views::Widget* window = CreateWindowWithContents(contents); 404 window->Show(); 405 406 // Send an alert event from the button and let the event loop run. 407 control_event_count_ = 0; 408 button->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 409 base::MessageLoop::current()->RunUntilIdle(); 410 411 EXPECT_EQ(kTypeAlert, last_control_type_); 412 EXPECT_EQ(1, control_event_count_); 413 EXPECT_EQ(kButtonASCII, last_control_name_); 414 415 // Send an alert event from the window and let the event loop run. 416 control_event_count_ = 0; 417 window->GetRootView()->NotifyAccessibilityEvent( 418 ui::AX_EVENT_ALERT, true); 419 base::MessageLoop::current()->RunUntilIdle(); 420 421 EXPECT_EQ(1, control_event_count_); 422 EXPECT_EQ(kTypeWindow, last_control_type_); 423 424 window->CloseNow(); 425} 426 427TEST_F(AccessibilityEventRouterViewsTest, AccessibilityFocusableView) { 428 // Create a view with a child view. 429 views::View* parent = new views::View(); 430 views::View* child = new views::View(); 431 parent->AddChildView(child); 432 433 // Put the view in a window. 434 views::Widget* window = CreateWindowWithContents(parent); 435 436 // Since the child view has no accessibility focusable ancestors, this 437 // should still be the child view. 438 views::View* accessible_view = 439 AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child); 440 EXPECT_EQ(accessible_view, child); 441 442 // Now make the parent view accessibility focusable. Calling 443 // FindFirstAccessibleAncestor() again on child should return the parent 444 // view. 445 parent->SetAccessibilityFocusable(true); 446 accessible_view = 447 AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child); 448 EXPECT_EQ(accessible_view, parent); 449 450 window->CloseNow(); 451} 452 453namespace { 454 455class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate { 456 public: 457 enum { 458 IDC_MENU_ITEM_1, 459 IDC_MENU_ITEM_2, 460 IDC_MENU_INVISIBLE, 461 IDC_MENU_ITEM_3, 462 }; 463 464 SimpleMenuDelegate() {} 465 virtual ~SimpleMenuDelegate() {} 466 467 views::MenuItemView* BuildMenu() { 468 menu_model_.reset(new ui::SimpleMenuModel(this)); 469 menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1")); 470 menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2")); 471 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 472 menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible")); 473 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 474 menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3")); 475 476 menu_adapter_.reset(new views::MenuModelAdapter(menu_model_.get())); 477 views::MenuItemView* menu_view = menu_adapter_->CreateMenu(); 478 menu_runner_.reset(new views::MenuRunner(menu_view, 0)); 479 return menu_view; 480 } 481 482 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE { 483 return false; 484 } 485 486 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { 487 return true; 488 } 489 490 virtual bool IsCommandIdVisible(int command_id) const OVERRIDE { 491 return command_id != IDC_MENU_INVISIBLE; 492 } 493 494 virtual bool GetAcceleratorForCommandId( 495 int command_id, 496 ui::Accelerator* accelerator) OVERRIDE { 497 return false; 498 } 499 500 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE { 501 } 502 503 private: 504 scoped_ptr<ui::SimpleMenuModel> menu_model_; 505 scoped_ptr<views::MenuModelAdapter> menu_adapter_; 506 scoped_ptr<views::MenuRunner> menu_runner_; 507 508 DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate); 509}; 510 511} // namespace 512 513TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) { 514 SimpleMenuDelegate menu_delegate; 515 views::MenuItemView* menu = menu_delegate.BuildMenu(); 516 views::View* menu_container = menu->CreateSubmenu(); 517 518 struct TestCase { 519 int command_id; 520 int expected_index; 521 int expected_count; 522 } kTestCases[] = { 523 { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 }, 524 { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 }, 525 { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 }, 526 { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 }, 527 }; 528 529 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { 530 int index = 0; 531 int count = 0; 532 533 AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount( 534 menu_container, 535 menu->GetMenuItemByID(kTestCases[i].command_id), 536 &index, 537 &count); 538 EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i; 539 EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i; 540 } 541} 542