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