accessibility_event_router_views_unittest.cc revision 3551c9c881056c480085172ff9840cab31610854
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/ui/views/accessibility/accessibility_event_router_views.h" 12#include "chrome/test/base/testing_profile.h" 13#include "testing/gtest/include/gtest/gtest.h" 14#include "ui/base/accessibility/accessible_view_state.h" 15#include "ui/views/controls/button/label_button.h" 16#include "ui/views/controls/label.h" 17#include "ui/views/layout/grid_layout.h" 18#include "ui/views/test/test_views_delegate.h" 19#include "ui/views/widget/native_widget.h" 20#include "ui/views/widget/root_view.h" 21#include "ui/views/widget/widget.h" 22#include "ui/views/widget/widget_delegate.h" 23 24#if defined(OS_WIN) 25#include "ui/base/win/scoped_ole_initializer.h" 26#endif 27 28#if defined(USE_AURA) 29#include "ui/aura/root_window.h" 30#include "ui/aura/test/aura_test_helper.h" 31#endif 32 33namespace { 34 35// The expected initial focus count. 36#if defined(OS_WIN) && !defined(USE_AURA) 37// On windows (non-aura) this code triggers activating the window. Activating 38// the window triggers clearing the focus then resetting it. This results in an 39// additional focus change. 40const int kInitialFocusCount = 2; 41#else 42const int kInitialFocusCount = 1; 43#endif 44 45} // namespace 46 47class AccessibilityViewsDelegate : public views::TestViewsDelegate { 48 public: 49 AccessibilityViewsDelegate() {} 50 virtual ~AccessibilityViewsDelegate() {} 51 52 // Overridden from views::TestViewsDelegate: 53 virtual void NotifyAccessibilityEvent( 54 views::View* view, ui::AccessibilityTypes::Event event_type) OVERRIDE { 55 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent( 56 view, event_type); 57 } 58 59 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate); 60}; 61 62class AccessibilityWindowDelegate : public views::WidgetDelegate { 63 public: 64 explicit AccessibilityWindowDelegate(views::View* contents) 65 : contents_(contents) { } 66 67 // Overridden from views::WidgetDelegate: 68 virtual void DeleteDelegate() OVERRIDE { delete this; } 69 virtual views::View* GetContentsView() OVERRIDE { return contents_; } 70 virtual const views::Widget* GetWidget() const OVERRIDE { 71 return contents_->GetWidget(); 72 } 73 virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); } 74 75 private: 76 views::View* contents_; 77 78 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate); 79}; 80 81class ViewWithNameAndRole : public views::View { 82 public: 83 explicit ViewWithNameAndRole(const string16& name, 84 ui::AccessibilityTypes::Role role) 85 : name_(name), 86 role_(role) { 87 } 88 89 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE { 90 views::View::GetAccessibleState(state); 91 state->name = name_; 92 state->role = role_; 93 } 94 95 void set_name(const string16& name) { name_ = name; } 96 97 private: 98 string16 name_; 99 ui::AccessibilityTypes::Role role_; 100 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole); 101}; 102 103class AccessibilityEventRouterViewsTest 104 : public testing::Test { 105 public: 106 AccessibilityEventRouterViewsTest() : focus_event_count_(0) { 107 } 108 109 virtual void SetUp() { 110#if defined(OS_WIN) 111 ole_initializer_.reset(new ui::ScopedOleInitializer()); 112#endif 113 views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate(); 114#if defined(USE_AURA) 115 aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_)); 116 aura_test_helper_->SetUp(); 117#endif // USE_AURA 118 EnableAccessibilityAndListenToFocusNotifications(); 119 } 120 121 virtual void TearDown() { 122 ClearCallback(); 123#if defined(USE_AURA) 124 aura_test_helper_->TearDown(); 125#endif 126 delete views::ViewsDelegate::views_delegate; 127 views::ViewsDelegate::views_delegate = NULL; 128 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(); 133 134#if defined(OS_WIN) 135 ole_initializer_.reset(); 136#endif 137 } 138 139 views::Widget* CreateWindowWithContents(views::View* contents) { 140 gfx::NativeView context = NULL; 141#if defined(USE_AURA) 142 context = aura_test_helper_->root_window(); 143#endif 144 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds( 145 new AccessibilityWindowDelegate(contents), 146 context, 147 gfx::Rect(0, 0, 500, 500)); 148 149 // Create a profile and associate it with this window. 150 widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_); 151 152 return widget; 153 } 154 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::OnFocusEvent, 162 base::Unretained(this))); 163 } 164 165 void ClearCallback() { 166 ExtensionAccessibilityEventRouter* accessibility_event_router = 167 ExtensionAccessibilityEventRouter::GetInstance(); 168 accessibility_event_router->ClearControlEventCallback(); 169 } 170 171 protected: 172 // Handle Focus event. 173 virtual void OnFocusEvent(ui::AccessibilityTypes::Event event, 174 const AccessibilityControlInfo* info) { 175 focus_event_count_++; 176 last_control_name_ = info->name(); 177 last_control_context_ = info->context(); 178 } 179 180 base::MessageLoopForUI message_loop_; 181 int focus_event_count_; 182 std::string last_control_name_; 183 std::string last_control_context_; 184 TestingProfile profile_; 185#if defined(OS_WIN) 186 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_; 187#endif 188#if defined(USE_AURA) 189 scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_; 190#endif 191}; 192 193TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) { 194 const char kButton1ASCII[] = "Button1"; 195 const char kButton2ASCII[] = "Button2"; 196 const char kButton3ASCII[] = "Button3"; 197 const char kButton3NewASCII[] = "Button3New"; 198 199 // Create a contents view with 3 buttons. 200 views::View* contents = new views::View(); 201 views::LabelButton* button1 = new views::LabelButton( 202 NULL, ASCIIToUTF16(kButton1ASCII)); 203 button1->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 204 contents->AddChildView(button1); 205 views::LabelButton* button2 = new views::LabelButton( 206 NULL, ASCIIToUTF16(kButton2ASCII)); 207 button2->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 208 contents->AddChildView(button2); 209 views::LabelButton* button3 = new views::LabelButton( 210 NULL, ASCIIToUTF16(kButton3ASCII)); 211 button3->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 212 contents->AddChildView(button3); 213 214 // Put the view in a window. 215 views::Widget* window = CreateWindowWithContents(contents); 216 window->Show(); 217 window->Activate(); 218 219 // Set focus to the first button initially and run message loop to execute 220 // callback. 221 button1->RequestFocus(); 222 base::MessageLoop::current()->RunUntilIdle(); 223 224 // Change the accessible name of button3. 225 button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII)); 226 227 // Advance focus to the next button and test that we got the 228 // expected notification with the name of button 2. 229 views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager(); 230 focus_event_count_ = 0; 231 focus_manager->AdvanceFocus(false); 232 base::MessageLoop::current()->RunUntilIdle(); 233 EXPECT_EQ(1, focus_event_count_); 234 EXPECT_EQ(kButton2ASCII, last_control_name_); 235 236 // Advance to button 3. Expect the new accessible name we assigned. 237 focus_manager->AdvanceFocus(false); 238 base::MessageLoop::current()->RunUntilIdle(); 239 EXPECT_EQ(2, focus_event_count_); 240 EXPECT_EQ(kButton3NewASCII, last_control_name_); 241 242 // Advance to button 1 and check the notification. 243 focus_manager->AdvanceFocus(false); 244 base::MessageLoop::current()->RunUntilIdle(); 245 EXPECT_EQ(3, focus_event_count_); 246 EXPECT_EQ(kButton1ASCII, last_control_name_); 247 248 window->CloseNow(); 249} 250 251TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) { 252 const char kToolbarNameASCII[] = "MyToolbar"; 253 const char kButtonNameASCII[] = "MyButton"; 254 255 // Create a toolbar with a button. 256 views::View* contents = new ViewWithNameAndRole( 257 ASCIIToUTF16(kToolbarNameASCII), 258 ui::AccessibilityTypes::ROLE_TOOLBAR); 259 views::LabelButton* button = new views::LabelButton( 260 NULL, ASCIIToUTF16(kButtonNameASCII)); 261 button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 262 contents->AddChildView(button); 263 264 // Put the view in a window. 265 views::Widget* window = CreateWindowWithContents(contents); 266 267 // Set focus to the button. 268 focus_event_count_ = 0; 269 button->RequestFocus(); 270 271 base::MessageLoop::current()->RunUntilIdle(); 272 273 // Test that we got the event with the expected name and context. 274 EXPECT_EQ(kInitialFocusCount, focus_event_count_); 275 EXPECT_EQ(kButtonNameASCII, last_control_name_); 276 EXPECT_EQ(kToolbarNameASCII, last_control_context_); 277 278 window->CloseNow(); 279} 280 281TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) { 282 const char kAlertTextASCII[] = "MyAlertText"; 283 const char kButtonNameASCII[] = "MyButton"; 284 285 // Create an alert with static text and a button, similar to an infobar. 286 views::View* contents = new ViewWithNameAndRole( 287 string16(), 288 ui::AccessibilityTypes::ROLE_ALERT); 289 views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII)); 290 contents->AddChildView(label); 291 views::LabelButton* button = new views::LabelButton( 292 NULL, ASCIIToUTF16(kButtonNameASCII)); 293 button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 294 contents->AddChildView(button); 295 296 // Put the view in a window. 297 views::Widget* window = CreateWindowWithContents(contents); 298 299 // Set focus to the button. 300 focus_event_count_ = 0; 301 button->RequestFocus(); 302 303 base::MessageLoop::current()->RunUntilIdle(); 304 305 // Test that we got the event with the expected name and context. 306 EXPECT_EQ(kInitialFocusCount, focus_event_count_); 307 EXPECT_EQ(kButtonNameASCII, last_control_name_); 308 EXPECT_EQ(kAlertTextASCII, last_control_context_); 309 310 window->CloseNow(); 311} 312 313TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) { 314 const char kContentsNameASCII[] = "Contents"; 315 const char kOldNameASCII[] = "OldName"; 316 const char kNewNameASCII[] = "NewName"; 317 318 // Create a toolbar with a button. 319 views::View* contents = new ViewWithNameAndRole( 320 ASCIIToUTF16(kContentsNameASCII), 321 ui::AccessibilityTypes::ROLE_CLIENT); 322 ViewWithNameAndRole* child = new ViewWithNameAndRole( 323 ASCIIToUTF16(kOldNameASCII), 324 ui::AccessibilityTypes::ROLE_PUSHBUTTON); 325 child->set_focusable(true); 326 contents->AddChildView(child); 327 328 // Put the view in a window. 329 views::Widget* window = CreateWindowWithContents(contents); 330 331 // Set focus to the child view. 332 focus_event_count_ = 0; 333 child->RequestFocus(); 334 335 // Change the child's name after the focus notification. 336 child->set_name(ASCIIToUTF16(kNewNameASCII)); 337 338 // We shouldn't get the notification right away. 339 EXPECT_EQ(0, focus_event_count_); 340 341 // Process anything in the event loop. Now we should get the notification, 342 // and it should give us the new control name, not the old one. 343 base::MessageLoop::current()->RunUntilIdle(); 344 EXPECT_EQ(kInitialFocusCount, focus_event_count_); 345 EXPECT_EQ(kNewNameASCII, last_control_name_); 346 347 window->CloseNow(); 348} 349 350TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) { 351 const char kContentsNameASCII[] = "Contents"; 352 const char kNameASCII[] = "OldName"; 353 354 // Create a toolbar with a button. 355 views::View* contents = new ViewWithNameAndRole( 356 ASCIIToUTF16(kContentsNameASCII), 357 ui::AccessibilityTypes::ROLE_CLIENT); 358 ViewWithNameAndRole* child = new ViewWithNameAndRole( 359 ASCIIToUTF16(kNameASCII), 360 ui::AccessibilityTypes::ROLE_PUSHBUTTON); 361 child->set_focusable(true); 362 contents->AddChildView(child); 363 364 // Put the view in a window. 365 views::Widget* window = CreateWindowWithContents(contents); 366 367 // Set focus to the child view. 368 focus_event_count_ = 0; 369 child->RequestFocus(); 370 371 // Delete the child! 372 delete child; 373 374 // We shouldn't get the notification right away. 375 EXPECT_EQ(0, focus_event_count_); 376 377 // Process anything in the event loop. We shouldn't get a notification 378 // because the view is no longer valid, and this shouldn't crash. 379 base::MessageLoop::current()->RunUntilIdle(); 380 EXPECT_EQ(0, focus_event_count_); 381 382 window->CloseNow(); 383} 384