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.
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"
32 #include "ui/base/win/scoped_ole_initializer.h"
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"
42 using base::ASCIIToUTF16;
44 class AccessibilityViewsDelegate : public views::TestViewsDelegate {
46 AccessibilityViewsDelegate() {}
47 ~AccessibilityViewsDelegate() override {}
49 // Overridden from views::TestViewsDelegate:
50 void NotifyAccessibilityEvent(views::View* view,
51 ui::AXEvent event_type) override {
52 AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
57 DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
60 class AccessibilityWindowDelegate : public views::WidgetDelegate {
62 explicit AccessibilityWindowDelegate(views::View* contents)
63 : contents_(contents) { }
65 // Overridden from views::WidgetDelegate:
66 void DeleteDelegate() override { delete this; }
67 views::View* GetContentsView() override { return contents_; }
68 const views::Widget* GetWidget() const override {
69 return contents_->GetWidget();
71 views::Widget* GetWidget() override { return contents_->GetWidget(); }
74 views::View* contents_;
76 DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
79 class ViewWithNameAndRole : public views::View {
81 explicit ViewWithNameAndRole(const base::string16& name,
87 void GetAccessibleState(ui::AXViewState* state) override {
88 views::View::GetAccessibleState(state);
93 void set_name(const base::string16& name) { name_ = name; }
98 DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
101 class AccessibilityEventRouterViewsTest
102 : public testing::Test {
104 AccessibilityEventRouterViewsTest() : control_event_count_(0) {
107 void SetUp() override {
109 ole_initializer_.reset(new ui::ScopedOleInitializer());
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);
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());
122 EnableAccessibilityAndListenToFocusNotifications();
125 void TearDown() override {
127 #if defined(USE_AURA)
128 aura_test_helper_->TearDown();
129 ui::TerminateContextFactoryForTests();
131 delete views::ViewsDelegate::views_delegate;
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();
139 ole_initializer_.reset();
143 views::Widget* CreateWindowWithContents(views::View* contents) {
144 gfx::NativeWindow context = NULL;
145 #if defined(USE_AURA)
146 context = aura_test_helper_->root_window();
148 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
149 new AccessibilityWindowDelegate(contents),
151 gfx::Rect(0, 0, 500, 500));
153 // Create a profile and associate it with this window.
154 widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
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)));
169 void ClearCallback() {
170 ExtensionAccessibilityEventRouter* accessibility_event_router =
171 ExtensionAccessibilityEventRouter::GetInstance();
172 accessibility_event_router->ClearControlEventCallback();
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();
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_;
192 scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
194 #if defined(USE_AURA)
195 scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
199 TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
200 const char kButton1ASCII[] = "Button1";
201 const char kButton2ASCII[] = "Button2";
202 const char kButton3ASCII[] = "Button3";
203 const char kButton3NewASCII[] = "Button3New";
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);
220 // Put the view in a window.
221 views::Widget* window = CreateWindowWithContents(contents);
224 // Set focus to the first button initially and run message loop to execute
226 button1->RequestFocus();
227 base::MessageLoop::current()->RunUntilIdle();
229 // Change the accessible name of button3.
230 button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
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_);
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_);
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_);
256 TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
257 const char kToolbarNameASCII[] = "MyToolbar";
258 const char kButtonNameASCII[] = "MyButton";
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);
269 // Put the view in a window.
270 views::Widget* window = CreateWindowWithContents(contents);
272 // Set focus to the button.
273 control_event_count_ = 0;
274 button->RequestFocus();
276 base::MessageLoop::current()->RunUntilIdle();
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_);
286 TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
287 const char kAlertTextASCII[] = "MyAlertText";
288 const char kButtonNameASCII[] = "MyButton";
290 // Create an alert with static text and a button, similar to an infobar.
291 views::View* contents = new ViewWithNameAndRole(
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);
301 // Put the view in a window.
302 views::Widget* window = CreateWindowWithContents(contents);
304 // Set focus to the button.
305 control_event_count_ = 0;
306 button->RequestFocus();
308 base::MessageLoop::current()->RunUntilIdle();
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_);
318 TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
319 const char kContentsNameASCII[] = "Contents";
320 const char kOldNameASCII[] = "OldName";
321 const char kNewNameASCII[] = "NewName";
323 // Create a toolbar with a button.
324 views::View* contents = new ViewWithNameAndRole(
325 ASCIIToUTF16(kContentsNameASCII),
327 ViewWithNameAndRole* child = new ViewWithNameAndRole(
328 ASCIIToUTF16(kOldNameASCII),
330 child->SetFocusable(true);
331 contents->AddChildView(child);
333 // Put the view in a window.
334 views::Widget* window = CreateWindowWithContents(contents);
336 // Set focus to the child view.
337 control_event_count_ = 0;
338 child->RequestFocus();
340 // Change the child's name after the focus notification.
341 child->set_name(ASCIIToUTF16(kNewNameASCII));
343 // We shouldn't get the notification right away.
344 EXPECT_EQ(0, control_event_count_);
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_);
355 TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
356 const char kContentsNameASCII[] = "Contents";
357 const char kNameASCII[] = "OldName";
359 // Create a toolbar with a button.
360 views::View* contents = new ViewWithNameAndRole(
361 ASCIIToUTF16(kContentsNameASCII),
363 ViewWithNameAndRole* child = new ViewWithNameAndRole(
364 ASCIIToUTF16(kNameASCII),
366 child->SetFocusable(true);
367 contents->AddChildView(child);
369 // Put the view in a window.
370 views::Widget* window = CreateWindowWithContents(contents);
372 // Set focus to the child view.
373 control_event_count_ = 0;
374 child->RequestFocus();
379 // We shouldn't get the notification right away.
380 EXPECT_EQ(0, control_event_count_);
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_);
390 TEST_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;
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);
402 // Put the view in a window.
403 views::Widget* window = CreateWindowWithContents(contents);
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();
411 EXPECT_EQ(kTypeAlert, last_control_type_);
412 EXPECT_EQ(1, control_event_count_);
413 EXPECT_EQ(kButtonASCII, last_control_name_);
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();
421 EXPECT_EQ(1, control_event_count_);
422 EXPECT_EQ(kTypeWindow, last_control_type_);
427 TEST_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);
433 // Put the view in a window.
434 views::Widget* window = CreateWindowWithContents(parent);
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);
442 // Now make the parent view accessibility focusable. Calling
443 // FindFirstAccessibleAncestor() again on child should return the parent
445 parent->SetAccessibilityFocusable(true);
447 AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child);
448 EXPECT_EQ(accessible_view, parent);
455 class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
464 SimpleMenuDelegate() {}
465 ~SimpleMenuDelegate() override {}
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"));
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));
482 bool IsCommandIdChecked(int command_id) const override { return false; }
484 bool IsCommandIdEnabled(int command_id) const override { return true; }
486 bool IsCommandIdVisible(int command_id) const override {
487 return command_id != IDC_MENU_INVISIBLE;
490 bool GetAcceleratorForCommandId(int command_id,
491 ui::Accelerator* accelerator) override {
495 void ExecuteCommand(int command_id, int event_flags) override {}
498 scoped_ptr<ui::SimpleMenuModel> menu_model_;
499 scoped_ptr<views::MenuModelAdapter> menu_adapter_;
500 scoped_ptr<views::MenuRunner> menu_runner_;
502 DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
507 TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
508 SimpleMenuDelegate menu_delegate;
509 views::MenuItemView* menu = menu_delegate.BuildMenu();
510 views::View* menu_container = menu->CreateSubmenu();
517 { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
518 { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
519 { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
520 { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
523 for (size_t i = 0; i < arraysize(kTestCases); ++i) {
527 AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
529 menu->GetMenuItemByID(kTestCases[i].command_id),
532 EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
533 EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;