accessibility_event_router_views_unittest.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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->set_focusable(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->set_focusable(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