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