accessibility_event_router_views_unittest.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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.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/common/chrome_notification_types.h" 13#include "chrome/test/base/testing_profile.h" 14#include "content/public/browser/notification_registrar.h" 15#include "content/public/browser/notification_service.h" 16#include "testing/gtest/include/gtest/gtest.h" 17#include "ui/base/accessibility/accessible_view_state.h" 18#include "ui/views/controls/button/label_button.h" 19#include "ui/views/controls/label.h" 20#include "ui/views/layout/grid_layout.h" 21#include "ui/views/test/test_views_delegate.h" 22#include "ui/views/widget/native_widget.h" 23#include "ui/views/widget/root_view.h" 24#include "ui/views/widget/widget.h" 25#include "ui/views/widget/widget_delegate.h" 26 27#if defined(OS_WIN) 28#include "ui/base/win/scoped_ole_initializer.h" 29#endif 30 31#if defined(USE_AURA) 32#include "ui/aura/root_window.h" 33#include "ui/aura/test/aura_test_helper.h" 34#endif 35 36namespace { 37 38// The expected initial focus count. 39#if defined(OS_WIN) && !defined(USE_AURA) 40// On windows (non-aura) this code triggers activating the window. Activating 41// the window triggers clearing the focus then resetting it. This results in an 42// additional focus change. 43const int kInitialFocusCount = 2; 44#else 45const int kInitialFocusCount = 1; 46#endif 47 48} // namespace 49 50class AccessibilityViewsDelegate : public views::TestViewsDelegate { 51 public: 52 AccessibilityViewsDelegate() {} 53 virtual ~AccessibilityViewsDelegate() {} 54 55 // Overridden from views::TestViewsDelegate: 56 virtual void NotifyAccessibilityEvent( 57 views::View* view, ui::AccessibilityTypes::Event event_type) OVERRIDE { 58 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent( 59 view, event_type); 60 } 61 62 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate); 63}; 64 65class AccessibilityWindowDelegate : public views::WidgetDelegate { 66 public: 67 explicit AccessibilityWindowDelegate(views::View* contents) 68 : contents_(contents) { } 69 70 // Overridden from views::WidgetDelegate: 71 virtual void DeleteDelegate() OVERRIDE { delete this; } 72 virtual views::View* GetContentsView() OVERRIDE { return contents_; } 73 virtual const views::Widget* GetWidget() const OVERRIDE { 74 return contents_->GetWidget(); 75 } 76 virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); } 77 78 private: 79 views::View* contents_; 80 81 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate); 82}; 83 84class ViewWithNameAndRole : public views::View { 85 public: 86 explicit ViewWithNameAndRole(const string16& name, 87 ui::AccessibilityTypes::Role role) 88 : name_(name), 89 role_(role) { 90 } 91 92 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE { 93 views::View::GetAccessibleState(state); 94 state->name = name_; 95 state->role = role_; 96 } 97 98 void set_name(const string16& name) { name_ = name; } 99 100 private: 101 string16 name_; 102 ui::AccessibilityTypes::Role role_; 103 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole); 104}; 105 106class AccessibilityEventRouterViewsTest 107 : public testing::Test, 108 public content::NotificationObserver { 109 public: 110 AccessibilityEventRouterViewsTest() { 111 } 112 113 virtual void SetUp() { 114#if defined(OS_WIN) 115 ole_initializer_.reset(new ui::ScopedOleInitializer()); 116#endif 117 views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate(); 118#if defined(USE_AURA) 119 aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_)); 120 aura_test_helper_->SetUp(); 121#endif // USE_AURA 122 } 123 124 virtual void TearDown() { 125#if defined(USE_AURA) 126 aura_test_helper_->TearDown(); 127#endif 128 delete views::ViewsDelegate::views_delegate; 129 views::ViewsDelegate::views_delegate = NULL; 130 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(); 135 136#if defined(OS_WIN) 137 ole_initializer_.reset(); 138#endif 139 } 140 141 views::Widget* CreateWindowWithContents(views::View* contents) { 142 gfx::NativeView context = NULL; 143#if defined(USE_AURA) 144 context = aura_test_helper_->root_window(); 145#endif 146 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds( 147 new AccessibilityWindowDelegate(contents), 148 context, 149 gfx::Rect(0, 0, 500, 500)); 150 151 // Create a profile and associate it with this window. 152 widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_); 153 154 return widget; 155 } 156 157 void EnableAccessibilityAndListenToFocusNotifications() { 158 registrar_.Add(this, 159 chrome::NOTIFICATION_ACCESSIBILITY_CONTROL_FOCUSED, 160 content::NotificationService::AllSources()); 161 162 // Switch on accessibility event notifications. 163 ExtensionAccessibilityEventRouter* accessibility_event_router = 164 ExtensionAccessibilityEventRouter::GetInstance(); 165 accessibility_event_router->SetAccessibilityEnabled(true); 166 } 167 168 protected: 169 // Implement NotificationObserver::Observe and store information about a 170 // ACCESSIBILITY_CONTROL_FOCUSED event. 171 virtual void Observe(int type, 172 const content::NotificationSource& source, 173 const content::NotificationDetails& details) OVERRIDE { 174 ASSERT_EQ(type, chrome::NOTIFICATION_ACCESSIBILITY_CONTROL_FOCUSED); 175 const AccessibilityControlInfo* info = 176 content::Details<const AccessibilityControlInfo>(details).ptr(); 177 focus_event_count_++; 178 last_control_name_ = info->name(); 179 last_control_context_ = info->context(); 180 } 181 182 base::MessageLoopForUI message_loop_; 183 int focus_event_count_; 184 std::string last_control_name_; 185 std::string last_control_context_; 186 content::NotificationRegistrar registrar_; 187 TestingProfile profile_; 188#if defined(OS_WIN) 189 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_; 190#endif 191#if defined(USE_AURA) 192 scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_; 193#endif 194}; 195 196TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) { 197 const char kButton1ASCII[] = "Button1"; 198 const char kButton2ASCII[] = "Button2"; 199 const char kButton3ASCII[] = "Button3"; 200 const char kButton3NewASCII[] = "Button3New"; 201 202 // Create a contents view with 3 buttons. 203 views::View* contents = new views::View(); 204 views::LabelButton* button1 = new views::LabelButton( 205 NULL, ASCIIToUTF16(kButton1ASCII)); 206 button1->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 207 contents->AddChildView(button1); 208 views::LabelButton* button2 = new views::LabelButton( 209 NULL, ASCIIToUTF16(kButton2ASCII)); 210 button2->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 211 contents->AddChildView(button2); 212 views::LabelButton* button3 = new views::LabelButton( 213 NULL, ASCIIToUTF16(kButton3ASCII)); 214 button3->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 215 contents->AddChildView(button3); 216 217 // Put the view in a window. 218 views::Widget* window = CreateWindowWithContents(contents); 219 window->Show(); 220 window->Activate(); 221 222 // Set focus to the first button initially. 223 button1->RequestFocus(); 224 225 EnableAccessibilityAndListenToFocusNotifications(); 226 227 // Change the accessible name of button3. 228 button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII)); 229 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 focus_event_count_ = 0; 234 focus_manager->AdvanceFocus(false); 235 base::MessageLoop::current()->RunUntilIdle(); 236 EXPECT_EQ(1, focus_event_count_); 237 EXPECT_EQ(kButton2ASCII, last_control_name_); 238 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, focus_event_count_); 243 EXPECT_EQ(kButton3NewASCII, last_control_name_); 244 245 // Advance to button 1 and check the notification. 246 focus_manager->AdvanceFocus(false); 247 base::MessageLoop::current()->RunUntilIdle(); 248 EXPECT_EQ(3, focus_event_count_); 249 EXPECT_EQ(kButton1ASCII, last_control_name_); 250 251 window->CloseNow(); 252} 253 254TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) { 255 const char kToolbarNameASCII[] = "MyToolbar"; 256 const char kButtonNameASCII[] = "MyButton"; 257 258 // Create a toolbar with a button. 259 views::View* contents = new ViewWithNameAndRole( 260 ASCIIToUTF16(kToolbarNameASCII), 261 ui::AccessibilityTypes::ROLE_TOOLBAR); 262 views::LabelButton* button = new views::LabelButton( 263 NULL, ASCIIToUTF16(kButtonNameASCII)); 264 button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 265 contents->AddChildView(button); 266 267 // Put the view in a window. 268 views::Widget* window = CreateWindowWithContents(contents); 269 270 EnableAccessibilityAndListenToFocusNotifications(); 271 272 // Set focus to the button. 273 focus_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(kInitialFocusCount, focus_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 string16(), 293 ui::AccessibilityTypes::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_NATIVE_TEXTBUTTON); 299 contents->AddChildView(button); 300 301 // Put the view in a window. 302 views::Widget* window = CreateWindowWithContents(contents); 303 304 EnableAccessibilityAndListenToFocusNotifications(); 305 306 // Set focus to the button. 307 focus_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, focus_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->set_focusable(true); 333 contents->AddChildView(child); 334 335 // Put the view in a window. 336 views::Widget* window = CreateWindowWithContents(contents); 337 338 EnableAccessibilityAndListenToFocusNotifications(); 339 340 // Set focus to the child view. 341 focus_event_count_ = 0; 342 child->RequestFocus(); 343 344 // Change the child's name after the focus notification. 345 child->set_name(ASCIIToUTF16(kNewNameASCII)); 346 347 // We shouldn't get the notification right away. 348 EXPECT_EQ(0, focus_event_count_); 349 350 // Process anything in the event loop. Now we should get the notification, 351 // and it should give us the new control name, not the old one. 352 base::MessageLoop::current()->RunUntilIdle(); 353 EXPECT_EQ(kInitialFocusCount, focus_event_count_); 354 EXPECT_EQ(kNewNameASCII, last_control_name_); 355 356 window->CloseNow(); 357} 358 359TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) { 360 const char kContentsNameASCII[] = "Contents"; 361 const char kNameASCII[] = "OldName"; 362 363 // Create a toolbar with a button. 364 views::View* contents = new ViewWithNameAndRole( 365 ASCIIToUTF16(kContentsNameASCII), 366 ui::AccessibilityTypes::ROLE_CLIENT); 367 ViewWithNameAndRole* child = new ViewWithNameAndRole( 368 ASCIIToUTF16(kNameASCII), 369 ui::AccessibilityTypes::ROLE_PUSHBUTTON); 370 child->set_focusable(true); 371 contents->AddChildView(child); 372 373 // Put the view in a window. 374 views::Widget* window = CreateWindowWithContents(contents); 375 376 EnableAccessibilityAndListenToFocusNotifications(); 377 378 // Set focus to the child view. 379 focus_event_count_ = 0; 380 child->RequestFocus(); 381 382 // Delete the child! 383 delete child; 384 385 // We shouldn't get the notification right away. 386 EXPECT_EQ(0, focus_event_count_); 387 388 // Process anything in the event loop. We shouldn't get a notification 389 // because the view is no longer valid, and this shouldn't crash. 390 base::MessageLoop::current()->RunUntilIdle(); 391 EXPECT_EQ(0, focus_event_count_); 392 393 window->CloseNow(); 394} 395