accessibility_event_router_views_unittest.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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_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/test/aura_test_helper.h"
36#include "ui/aura/window_event_dispatcher.h"
37#include "ui/compositor/test/context_factories_for_test.h"
38#include "ui/wm/core/default_activation_client.h"
39#endif
40
41using base::ASCIIToUTF16;
42
43class AccessibilityViewsDelegate : public views::TestViewsDelegate {
44 public:
45  AccessibilityViewsDelegate() {}
46  virtual ~AccessibilityViewsDelegate() {}
47
48  // Overridden from views::TestViewsDelegate:
49  virtual void NotifyAccessibilityEvent(
50      views::View* view, ui::AXEvent event_type) OVERRIDE {
51    AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
52        view, event_type);
53  }
54
55 private:
56  DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
57};
58
59class AccessibilityWindowDelegate : public views::WidgetDelegate {
60 public:
61  explicit AccessibilityWindowDelegate(views::View* contents)
62      : contents_(contents) { }
63
64  // Overridden from views::WidgetDelegate:
65  virtual void DeleteDelegate() OVERRIDE { delete this; }
66  virtual views::View* GetContentsView() OVERRIDE { return contents_; }
67  virtual const views::Widget* GetWidget() const OVERRIDE {
68    return contents_->GetWidget();
69  }
70  virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
71
72 private:
73  views::View* contents_;
74
75  DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
76};
77
78class ViewWithNameAndRole : public views::View {
79 public:
80  explicit ViewWithNameAndRole(const base::string16& name,
81                               ui::AXRole role)
82      : name_(name),
83        role_(role) {
84  }
85
86  virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
87    views::View::GetAccessibleState(state);
88    state->name = name_;
89    state->role = role_;
90  }
91
92  void set_name(const base::string16& name) { name_ = name; }
93
94 private:
95  base::string16 name_;
96  ui::AXRole role_;
97  DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
98};
99
100class AccessibilityEventRouterViewsTest
101    : public testing::Test {
102 public:
103  AccessibilityEventRouterViewsTest() : control_event_count_(0) {
104  }
105
106  virtual void SetUp() {
107#if defined(OS_WIN)
108    ole_initializer_.reset(new ui::ScopedOleInitializer());
109#endif
110    views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
111#if defined(USE_AURA)
112    // The ContextFactory must exist before any Compositors are created.
113    bool enable_pixel_output = false;
114    ui::ContextFactory* context_factory =
115        ui::InitializeContextFactoryForTests(enable_pixel_output);
116
117    aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
118    aura_test_helper_->SetUp(context_factory);
119    new wm::DefaultActivationClient(aura_test_helper_->root_window());
120#endif  // USE_AURA
121    EnableAccessibilityAndListenToFocusNotifications();
122  }
123
124  virtual void TearDown() {
125    ClearCallback();
126#if defined(USE_AURA)
127    aura_test_helper_->TearDown();
128    ui::TerminateContextFactoryForTests();
129#endif
130    delete views::ViewsDelegate::views_delegate;
131
132    // The Widget's FocusManager is deleted using DeleteSoon - this
133    // forces it to be deleted now, so we don't have any memory leaks
134    // when this method exits.
135    base::MessageLoop::current()->RunUntilIdle();
136
137#if defined(OS_WIN)
138    ole_initializer_.reset();
139#endif
140  }
141
142  views::Widget* CreateWindowWithContents(views::View* contents) {
143    gfx::NativeView context = NULL;
144#if defined(USE_AURA)
145    context = aura_test_helper_->root_window();
146#endif
147    views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
148        new AccessibilityWindowDelegate(contents),
149        context,
150        gfx::Rect(0, 0, 500, 500));
151
152    // Create a profile and associate it with this window.
153    widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
154
155    return widget;
156  }
157
158  void EnableAccessibilityAndListenToFocusNotifications() {
159    // Switch on accessibility event notifications.
160    ExtensionAccessibilityEventRouter* accessibility_event_router =
161        ExtensionAccessibilityEventRouter::GetInstance();
162    accessibility_event_router->SetAccessibilityEnabled(true);
163    accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
164        &AccessibilityEventRouterViewsTest::OnControlEvent,
165        base::Unretained(this)));
166  }
167
168  void ClearCallback() {
169    ExtensionAccessibilityEventRouter* accessibility_event_router =
170        ExtensionAccessibilityEventRouter::GetInstance();
171    accessibility_event_router->ClearControlEventCallback();
172  }
173
174 protected:
175  // Handle Focus event.
176  virtual void OnControlEvent(ui::AXEvent event,
177                            const AccessibilityControlInfo* info) {
178    control_event_count_++;
179    last_control_type_ = info->type();
180    last_control_name_ = info->name();
181    last_control_context_ = info->context();
182  }
183
184  base::MessageLoopForUI message_loop_;
185  int control_event_count_;
186  std::string last_control_type_;
187  std::string last_control_name_;
188  std::string last_control_context_;
189  TestingProfile profile_;
190#if defined(OS_WIN)
191  scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
192#endif
193#if defined(USE_AURA)
194  scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
195#endif
196};
197
198TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
199  const char kButton1ASCII[] = "Button1";
200  const char kButton2ASCII[] = "Button2";
201  const char kButton3ASCII[] = "Button3";
202  const char kButton3NewASCII[] = "Button3New";
203
204  // Create a contents view with 3 buttons.
205  views::View* contents = new views::View();
206  views::LabelButton* button1 = new views::LabelButton(
207      NULL, ASCIIToUTF16(kButton1ASCII));
208  button1->SetStyle(views::Button::STYLE_BUTTON);
209  contents->AddChildView(button1);
210  views::LabelButton* button2 = new views::LabelButton(
211      NULL, ASCIIToUTF16(kButton2ASCII));
212  button2->SetStyle(views::Button::STYLE_BUTTON);
213  contents->AddChildView(button2);
214  views::LabelButton* button3 = new views::LabelButton(
215      NULL, ASCIIToUTF16(kButton3ASCII));
216  button3->SetStyle(views::Button::STYLE_BUTTON);
217  contents->AddChildView(button3);
218
219  // Put the view in a window.
220  views::Widget* window = CreateWindowWithContents(contents);
221  window->Show();
222
223  // Set focus to the first button initially and run message loop to execute
224  // callback.
225  button1->RequestFocus();
226  base::MessageLoop::current()->RunUntilIdle();
227
228  // Change the accessible name of button3.
229  button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
230
231  // Advance focus to the next button and test that we got the
232  // expected notification with the name of button 2.
233  views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
234  control_event_count_ = 0;
235  focus_manager->AdvanceFocus(false);
236  base::MessageLoop::current()->RunUntilIdle();
237  EXPECT_EQ(1, control_event_count_);
238  EXPECT_EQ(kButton2ASCII, last_control_name_);
239
240  // Advance to button 3. Expect the new accessible name we assigned.
241  focus_manager->AdvanceFocus(false);
242  base::MessageLoop::current()->RunUntilIdle();
243  EXPECT_EQ(2, control_event_count_);
244  EXPECT_EQ(kButton3NewASCII, last_control_name_);
245
246  // Advance to button 1 and check the notification.
247  focus_manager->AdvanceFocus(false);
248  base::MessageLoop::current()->RunUntilIdle();
249  EXPECT_EQ(3, control_event_count_);
250  EXPECT_EQ(kButton1ASCII, last_control_name_);
251
252  window->CloseNow();
253}
254
255TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
256  const char kToolbarNameASCII[] = "MyToolbar";
257  const char kButtonNameASCII[] = "MyButton";
258
259  // Create a toolbar with a button.
260  views::View* contents = new ViewWithNameAndRole(
261      ASCIIToUTF16(kToolbarNameASCII),
262      ui::AX_ROLE_TOOLBAR);
263  views::LabelButton* button = new views::LabelButton(
264      NULL, ASCIIToUTF16(kButtonNameASCII));
265  button->SetStyle(views::Button::STYLE_BUTTON);
266  contents->AddChildView(button);
267
268  // Put the view in a window.
269  views::Widget* window = CreateWindowWithContents(contents);
270
271  // Set focus to the button.
272  control_event_count_ = 0;
273  button->RequestFocus();
274
275  base::MessageLoop::current()->RunUntilIdle();
276
277  // Test that we got the event with the expected name and context.
278  EXPECT_EQ(1, control_event_count_);
279  EXPECT_EQ(kButtonNameASCII, last_control_name_);
280  EXPECT_EQ(kToolbarNameASCII, last_control_context_);
281
282  window->CloseNow();
283}
284
285TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
286  const char kAlertTextASCII[] = "MyAlertText";
287  const char kButtonNameASCII[] = "MyButton";
288
289  // Create an alert with static text and a button, similar to an infobar.
290  views::View* contents = new ViewWithNameAndRole(
291      base::string16(),
292      ui::AX_ROLE_ALERT);
293  views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
294  contents->AddChildView(label);
295  views::LabelButton* button = new views::LabelButton(
296      NULL, ASCIIToUTF16(kButtonNameASCII));
297  button->SetStyle(views::Button::STYLE_BUTTON);
298  contents->AddChildView(button);
299
300  // Put the view in a window.
301  views::Widget* window = CreateWindowWithContents(contents);
302
303  // Set focus to the button.
304  control_event_count_ = 0;
305  button->RequestFocus();
306
307  base::MessageLoop::current()->RunUntilIdle();
308
309  // Test that we got the event with the expected name and context.
310  EXPECT_EQ(1, control_event_count_);
311  EXPECT_EQ(kButtonNameASCII, last_control_name_);
312  EXPECT_EQ(kAlertTextASCII, last_control_context_);
313
314  window->CloseNow();
315}
316
317TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
318  const char kContentsNameASCII[] = "Contents";
319  const char kOldNameASCII[] = "OldName";
320  const char kNewNameASCII[] = "NewName";
321
322  // Create a toolbar with a button.
323  views::View* contents = new ViewWithNameAndRole(
324      ASCIIToUTF16(kContentsNameASCII),
325      ui::AX_ROLE_CLIENT);
326  ViewWithNameAndRole* child = new ViewWithNameAndRole(
327      ASCIIToUTF16(kOldNameASCII),
328      ui::AX_ROLE_BUTTON);
329  child->SetFocusable(true);
330  contents->AddChildView(child);
331
332  // Put the view in a window.
333  views::Widget* window = CreateWindowWithContents(contents);
334
335  // Set focus to the child view.
336  control_event_count_ = 0;
337  child->RequestFocus();
338
339  // Change the child's name after the focus notification.
340  child->set_name(ASCIIToUTF16(kNewNameASCII));
341
342  // We shouldn't get the notification right away.
343  EXPECT_EQ(0, control_event_count_);
344
345  // Process anything in the event loop. Now we should get the notification,
346  // and it should give us the new control name, not the old one.
347  base::MessageLoop::current()->RunUntilIdle();
348  EXPECT_EQ(1, control_event_count_);
349  EXPECT_EQ(kNewNameASCII, last_control_name_);
350
351  window->CloseNow();
352}
353
354TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
355  const char kContentsNameASCII[] = "Contents";
356  const char kNameASCII[] = "OldName";
357
358  // Create a toolbar with a button.
359  views::View* contents = new ViewWithNameAndRole(
360      ASCIIToUTF16(kContentsNameASCII),
361      ui::AX_ROLE_CLIENT);
362  ViewWithNameAndRole* child = new ViewWithNameAndRole(
363      ASCIIToUTF16(kNameASCII),
364      ui::AX_ROLE_BUTTON);
365  child->SetFocusable(true);
366  contents->AddChildView(child);
367
368  // Put the view in a window.
369  views::Widget* window = CreateWindowWithContents(contents);
370
371  // Set focus to the child view.
372  control_event_count_ = 0;
373  child->RequestFocus();
374
375  // Delete the child!
376  delete child;
377
378  // We shouldn't get the notification right away.
379  EXPECT_EQ(0, control_event_count_);
380
381  // Process anything in the event loop. We shouldn't get a notification
382  // because the view is no longer valid, and this shouldn't crash.
383  base::MessageLoop::current()->RunUntilIdle();
384  EXPECT_EQ(0, control_event_count_);
385
386  window->CloseNow();
387}
388
389TEST_F(AccessibilityEventRouterViewsTest, AlertsFromWindowAndControl) {
390  const char kButtonASCII[] = "Button";
391  const char* kTypeAlert = extension_accessibility_api_constants::kTypeAlert;
392  const char* kTypeWindow = extension_accessibility_api_constants::kTypeWindow;
393
394  // Create a contents view with a button.
395  views::View* contents = new views::View();
396  views::LabelButton* button = new views::LabelButton(
397      NULL, ASCIIToUTF16(kButtonASCII));
398  button->SetStyle(views::Button::STYLE_BUTTON);
399  contents->AddChildView(button);
400
401  // Put the view in a window.
402  views::Widget* window = CreateWindowWithContents(contents);
403  window->Show();
404
405  // Send an alert event from the button and let the event loop run.
406  control_event_count_ = 0;
407  button->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
408  base::MessageLoop::current()->RunUntilIdle();
409
410  EXPECT_EQ(kTypeAlert, last_control_type_);
411  EXPECT_EQ(1, control_event_count_);
412  EXPECT_EQ(kButtonASCII, last_control_name_);
413
414  // Send an alert event from the window and let the event loop run.
415  control_event_count_ = 0;
416  window->GetRootView()->NotifyAccessibilityEvent(
417      ui::AX_EVENT_ALERT, true);
418  base::MessageLoop::current()->RunUntilIdle();
419
420  EXPECT_EQ(1, control_event_count_);
421  EXPECT_EQ(kTypeWindow, last_control_type_);
422
423  window->CloseNow();
424}
425
426namespace {
427
428class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
429 public:
430  enum {
431    IDC_MENU_ITEM_1,
432    IDC_MENU_ITEM_2,
433    IDC_MENU_INVISIBLE,
434    IDC_MENU_ITEM_3,
435  };
436
437  SimpleMenuDelegate() {}
438  virtual ~SimpleMenuDelegate() {}
439
440  views::MenuItemView* BuildMenu() {
441    menu_model_.reset(new ui::SimpleMenuModel(this));
442    menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
443    menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
444    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
445    menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
446    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
447    menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));
448
449    menu_runner_.reset(new views::MenuRunner(menu_model_.get()));
450    return menu_runner_->GetMenu();
451  }
452
453  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
454    return false;
455  }
456
457  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
458    return true;
459  }
460
461  virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
462    return command_id != IDC_MENU_INVISIBLE;
463  }
464
465  virtual bool GetAcceleratorForCommandId(
466      int command_id,
467      ui::Accelerator* accelerator) OVERRIDE {
468    return false;
469  }
470
471  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
472  }
473
474 private:
475  scoped_ptr<ui::SimpleMenuModel> menu_model_;
476  scoped_ptr<views::MenuRunner> menu_runner_;
477
478  DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
479};
480
481}  // namespace
482
483TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
484  SimpleMenuDelegate menu_delegate;
485  views::MenuItemView* menu = menu_delegate.BuildMenu();
486  views::View* menu_container = menu->CreateSubmenu();
487
488  struct TestCase {
489    int command_id;
490    int expected_index;
491    int expected_count;
492  } kTestCases[] = {
493    { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
494    { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
495    { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
496    { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
497  };
498
499  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
500    int index = 0;
501    int count = 0;
502
503    AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
504        menu_container,
505        menu->GetMenuItemByID(kTestCases[i].command_id),
506        &index,
507        &count);
508    EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
509    EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;
510  }
511}
512