accessibility_event_router_views_unittest.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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_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"
30
31#if defined(OS_WIN)
32#include "ui/base/win/scoped_ole_initializer.h"
33#endif
34
35#if defined(USE_AURA)
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"
40#endif
41
42using base::ASCIIToUTF16;
43
44class AccessibilityViewsDelegate : public views::TestViewsDelegate {
45 public:
46  AccessibilityViewsDelegate() {}
47  virtual ~AccessibilityViewsDelegate() {}
48
49  // Overridden from views::TestViewsDelegate:
50  virtual void NotifyAccessibilityEvent(
51      views::View* view, ui::AXEvent event_type) OVERRIDE {
52    AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
53        view, event_type);
54  }
55
56 private:
57  DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
58};
59
60class AccessibilityWindowDelegate : public views::WidgetDelegate {
61 public:
62  explicit AccessibilityWindowDelegate(views::View* contents)
63      : contents_(contents) { }
64
65  // Overridden from views::WidgetDelegate:
66  virtual void DeleteDelegate() OVERRIDE { delete this; }
67  virtual views::View* GetContentsView() OVERRIDE { return contents_; }
68  virtual const views::Widget* GetWidget() const OVERRIDE {
69    return contents_->GetWidget();
70  }
71  virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
72
73 private:
74  views::View* contents_;
75
76  DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
77};
78
79class ViewWithNameAndRole : public views::View {
80 public:
81  explicit ViewWithNameAndRole(const base::string16& name,
82                               ui::AXRole role)
83      : name_(name),
84        role_(role) {
85  }
86
87  virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
88    views::View::GetAccessibleState(state);
89    state->name = name_;
90    state->role = role_;
91  }
92
93  void set_name(const base::string16& name) { name_ = name; }
94
95 private:
96  base::string16 name_;
97  ui::AXRole role_;
98  DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
99};
100
101class AccessibilityEventRouterViewsTest
102    : public testing::Test {
103 public:
104  AccessibilityEventRouterViewsTest() : control_event_count_(0) {
105  }
106
107  virtual void SetUp() {
108#if defined(OS_WIN)
109    ole_initializer_.reset(new ui::ScopedOleInitializer());
110#endif
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);
117
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());
121#endif  // USE_AURA
122    EnableAccessibilityAndListenToFocusNotifications();
123  }
124
125  virtual void TearDown() {
126    ClearCallback();
127#if defined(USE_AURA)
128    aura_test_helper_->TearDown();
129    ui::TerminateContextFactoryForTests();
130#endif
131    delete views::ViewsDelegate::views_delegate;
132
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();
137
138#if defined(OS_WIN)
139    ole_initializer_.reset();
140#endif
141  }
142
143  views::Widget* CreateWindowWithContents(views::View* contents) {
144    gfx::NativeWindow context = NULL;
145#if defined(USE_AURA)
146    context = aura_test_helper_->root_window();
147#endif
148    views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
149        new AccessibilityWindowDelegate(contents),
150        context,
151        gfx::Rect(0, 0, 500, 500));
152
153    // Create a profile and associate it with this window.
154    widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
155
156    return widget;
157  }
158
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)));
167  }
168
169  void ClearCallback() {
170    ExtensionAccessibilityEventRouter* accessibility_event_router =
171        ExtensionAccessibilityEventRouter::GetInstance();
172    accessibility_event_router->ClearControlEventCallback();
173  }
174
175 protected:
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();
183  }
184
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_;
191#if defined(OS_WIN)
192  scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
193#endif
194#if defined(USE_AURA)
195  scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
196#endif
197};
198
199TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
200  const char kButton1ASCII[] = "Button1";
201  const char kButton2ASCII[] = "Button2";
202  const char kButton3ASCII[] = "Button3";
203  const char kButton3NewASCII[] = "Button3New";
204
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);
219
220  // Put the view in a window.
221  views::Widget* window = CreateWindowWithContents(contents);
222  window->Show();
223
224  // Set focus to the first button initially and run message loop to execute
225  // callback.
226  button1->RequestFocus();
227  base::MessageLoop::current()->RunUntilIdle();
228
229  // Change the accessible name of button3.
230  button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
231
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_);
240
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_);
246
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_);
252
253  window->CloseNow();
254}
255
256TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
257  const char kToolbarNameASCII[] = "MyToolbar";
258  const char kButtonNameASCII[] = "MyButton";
259
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);
268
269  // Put the view in a window.
270  views::Widget* window = CreateWindowWithContents(contents);
271
272  // Set focus to the button.
273  control_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(1, control_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      base::string16(),
293      ui::AX_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_BUTTON);
299  contents->AddChildView(button);
300
301  // Put the view in a window.
302  views::Widget* window = CreateWindowWithContents(contents);
303
304  // Set focus to the button.
305  control_event_count_ = 0;
306  button->RequestFocus();
307
308  base::MessageLoop::current()->RunUntilIdle();
309
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_);
314
315  window->CloseNow();
316}
317
318TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
319  const char kContentsNameASCII[] = "Contents";
320  const char kOldNameASCII[] = "OldName";
321  const char kNewNameASCII[] = "NewName";
322
323  // Create a toolbar with a button.
324  views::View* contents = new ViewWithNameAndRole(
325      ASCIIToUTF16(kContentsNameASCII),
326      ui::AX_ROLE_CLIENT);
327  ViewWithNameAndRole* child = new ViewWithNameAndRole(
328      ASCIIToUTF16(kOldNameASCII),
329      ui::AX_ROLE_BUTTON);
330  child->SetFocusable(true);
331  contents->AddChildView(child);
332
333  // Put the view in a window.
334  views::Widget* window = CreateWindowWithContents(contents);
335
336  // Set focus to the child view.
337  control_event_count_ = 0;
338  child->RequestFocus();
339
340  // Change the child's name after the focus notification.
341  child->set_name(ASCIIToUTF16(kNewNameASCII));
342
343  // We shouldn't get the notification right away.
344  EXPECT_EQ(0, control_event_count_);
345
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_);
351
352  window->CloseNow();
353}
354
355TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
356  const char kContentsNameASCII[] = "Contents";
357  const char kNameASCII[] = "OldName";
358
359  // Create a toolbar with a button.
360  views::View* contents = new ViewWithNameAndRole(
361      ASCIIToUTF16(kContentsNameASCII),
362      ui::AX_ROLE_CLIENT);
363  ViewWithNameAndRole* child = new ViewWithNameAndRole(
364      ASCIIToUTF16(kNameASCII),
365      ui::AX_ROLE_BUTTON);
366  child->SetFocusable(true);
367  contents->AddChildView(child);
368
369  // Put the view in a window.
370  views::Widget* window = CreateWindowWithContents(contents);
371
372  // Set focus to the child view.
373  control_event_count_ = 0;
374  child->RequestFocus();
375
376  // Delete the child!
377  delete child;
378
379  // We shouldn't get the notification right away.
380  EXPECT_EQ(0, control_event_count_);
381
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_);
386
387  window->CloseNow();
388}
389
390TEST_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;
394
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);
401
402  // Put the view in a window.
403  views::Widget* window = CreateWindowWithContents(contents);
404  window->Show();
405
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();
410
411  EXPECT_EQ(kTypeAlert, last_control_type_);
412  EXPECT_EQ(1, control_event_count_);
413  EXPECT_EQ(kButtonASCII, last_control_name_);
414
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();
420
421  EXPECT_EQ(1, control_event_count_);
422  EXPECT_EQ(kTypeWindow, last_control_type_);
423
424  window->CloseNow();
425}
426
427namespace {
428
429class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
430 public:
431  enum {
432    IDC_MENU_ITEM_1,
433    IDC_MENU_ITEM_2,
434    IDC_MENU_INVISIBLE,
435    IDC_MENU_ITEM_3,
436  };
437
438  SimpleMenuDelegate() {}
439  virtual ~SimpleMenuDelegate() {}
440
441  views::MenuItemView* BuildMenu() {
442    menu_model_.reset(new ui::SimpleMenuModel(this));
443    menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
444    menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
445    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
446    menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
447    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
448    menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));
449
450    menu_adapter_.reset(new views::MenuModelAdapter(menu_model_.get()));
451    views::MenuItemView* menu_view = menu_adapter_->CreateMenu();
452    menu_runner_.reset(new views::MenuRunner(menu_view, 0));
453    return menu_view;
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::MenuModelAdapter> menu_adapter_;
480  scoped_ptr<views::MenuRunner> menu_runner_;
481
482  DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
483};
484
485}  // namespace
486
487TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
488  SimpleMenuDelegate menu_delegate;
489  views::MenuItemView* menu = menu_delegate.BuildMenu();
490  views::View* menu_container = menu->CreateSubmenu();
491
492  struct TestCase {
493    int command_id;
494    int expected_index;
495    int expected_count;
496  } kTestCases[] = {
497    { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
498    { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
499    { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
500    { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
501  };
502
503  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
504    int index = 0;
505    int count = 0;
506
507    AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
508        menu_container,
509        menu->GetMenuItemByID(kTestCases[i].command_id),
510        &index,
511        &count);
512    EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
513    EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;
514  }
515}
516