accessibility_event_router_views_unittest.cc revision 3551c9c881056c480085172ff9840cab31610854
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/ui/views/accessibility/accessibility_event_router_views.h"
12#include "chrome/test/base/testing_profile.h"
13#include "testing/gtest/include/gtest/gtest.h"
14#include "ui/base/accessibility/accessible_view_state.h"
15#include "ui/views/controls/button/label_button.h"
16#include "ui/views/controls/label.h"
17#include "ui/views/layout/grid_layout.h"
18#include "ui/views/test/test_views_delegate.h"
19#include "ui/views/widget/native_widget.h"
20#include "ui/views/widget/root_view.h"
21#include "ui/views/widget/widget.h"
22#include "ui/views/widget/widget_delegate.h"
23
24#if defined(OS_WIN)
25#include "ui/base/win/scoped_ole_initializer.h"
26#endif
27
28#if defined(USE_AURA)
29#include "ui/aura/root_window.h"
30#include "ui/aura/test/aura_test_helper.h"
31#endif
32
33namespace {
34
35// The expected initial focus count.
36#if defined(OS_WIN) && !defined(USE_AURA)
37// On windows (non-aura) this code triggers activating the window. Activating
38// the window triggers clearing the focus then resetting it. This results in an
39// additional focus change.
40const int kInitialFocusCount = 2;
41#else
42const int kInitialFocusCount = 1;
43#endif
44
45}  // namespace
46
47class AccessibilityViewsDelegate : public views::TestViewsDelegate {
48 public:
49  AccessibilityViewsDelegate() {}
50  virtual ~AccessibilityViewsDelegate() {}
51
52  // Overridden from views::TestViewsDelegate:
53  virtual void NotifyAccessibilityEvent(
54      views::View* view, ui::AccessibilityTypes::Event event_type) OVERRIDE {
55    AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
56        view, event_type);
57  }
58
59  DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
60};
61
62class AccessibilityWindowDelegate : public views::WidgetDelegate {
63 public:
64  explicit AccessibilityWindowDelegate(views::View* contents)
65      : contents_(contents) { }
66
67  // Overridden from views::WidgetDelegate:
68  virtual void DeleteDelegate() OVERRIDE { delete this; }
69  virtual views::View* GetContentsView() OVERRIDE { return contents_; }
70  virtual const views::Widget* GetWidget() const OVERRIDE {
71    return contents_->GetWidget();
72  }
73  virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
74
75 private:
76  views::View* contents_;
77
78  DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
79};
80
81class ViewWithNameAndRole : public views::View {
82 public:
83  explicit ViewWithNameAndRole(const string16& name,
84                               ui::AccessibilityTypes::Role role)
85      : name_(name),
86        role_(role) {
87  }
88
89  virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
90    views::View::GetAccessibleState(state);
91    state->name = name_;
92    state->role = role_;
93  }
94
95  void set_name(const string16& name) { name_ = name; }
96
97 private:
98  string16 name_;
99  ui::AccessibilityTypes::Role role_;
100  DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
101};
102
103class AccessibilityEventRouterViewsTest
104    : public testing::Test {
105 public:
106  AccessibilityEventRouterViewsTest() : focus_event_count_(0) {
107  }
108
109  virtual void SetUp() {
110#if defined(OS_WIN)
111    ole_initializer_.reset(new ui::ScopedOleInitializer());
112#endif
113    views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
114#if defined(USE_AURA)
115    aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
116    aura_test_helper_->SetUp();
117#endif  // USE_AURA
118    EnableAccessibilityAndListenToFocusNotifications();
119  }
120
121  virtual void TearDown() {
122    ClearCallback();
123#if defined(USE_AURA)
124    aura_test_helper_->TearDown();
125#endif
126    delete views::ViewsDelegate::views_delegate;
127    views::ViewsDelegate::views_delegate = NULL;
128
129    // The Widget's FocusManager is deleted using DeleteSoon - this
130    // forces it to be deleted now, so we don't have any memory leaks
131    // when this method exits.
132    base::MessageLoop::current()->RunUntilIdle();
133
134#if defined(OS_WIN)
135    ole_initializer_.reset();
136#endif
137  }
138
139  views::Widget* CreateWindowWithContents(views::View* contents) {
140    gfx::NativeView context = NULL;
141#if defined(USE_AURA)
142    context = aura_test_helper_->root_window();
143#endif
144    views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
145        new AccessibilityWindowDelegate(contents),
146        context,
147        gfx::Rect(0, 0, 500, 500));
148
149    // Create a profile and associate it with this window.
150    widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
151
152    return widget;
153  }
154
155  void EnableAccessibilityAndListenToFocusNotifications() {
156    // Switch on accessibility event notifications.
157    ExtensionAccessibilityEventRouter* accessibility_event_router =
158        ExtensionAccessibilityEventRouter::GetInstance();
159    accessibility_event_router->SetAccessibilityEnabled(true);
160    accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
161        &AccessibilityEventRouterViewsTest::OnFocusEvent,
162        base::Unretained(this)));
163  }
164
165  void ClearCallback() {
166    ExtensionAccessibilityEventRouter* accessibility_event_router =
167        ExtensionAccessibilityEventRouter::GetInstance();
168    accessibility_event_router->ClearControlEventCallback();
169  }
170
171 protected:
172  // Handle Focus event.
173  virtual void OnFocusEvent(ui::AccessibilityTypes::Event event,
174                            const AccessibilityControlInfo* info) {
175    focus_event_count_++;
176    last_control_name_ = info->name();
177    last_control_context_ = info->context();
178  }
179
180  base::MessageLoopForUI message_loop_;
181  int focus_event_count_;
182  std::string last_control_name_;
183  std::string last_control_context_;
184  TestingProfile profile_;
185#if defined(OS_WIN)
186  scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
187#endif
188#if defined(USE_AURA)
189  scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
190#endif
191};
192
193TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
194  const char kButton1ASCII[] = "Button1";
195  const char kButton2ASCII[] = "Button2";
196  const char kButton3ASCII[] = "Button3";
197  const char kButton3NewASCII[] = "Button3New";
198
199  // Create a contents view with 3 buttons.
200  views::View* contents = new views::View();
201  views::LabelButton* button1 = new views::LabelButton(
202      NULL, ASCIIToUTF16(kButton1ASCII));
203  button1->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
204  contents->AddChildView(button1);
205  views::LabelButton* button2 = new views::LabelButton(
206      NULL, ASCIIToUTF16(kButton2ASCII));
207  button2->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
208  contents->AddChildView(button2);
209  views::LabelButton* button3 = new views::LabelButton(
210      NULL, ASCIIToUTF16(kButton3ASCII));
211  button3->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
212  contents->AddChildView(button3);
213
214  // Put the view in a window.
215  views::Widget* window = CreateWindowWithContents(contents);
216  window->Show();
217  window->Activate();
218
219  // Set focus to the first button initially and run message loop to execute
220  // callback.
221  button1->RequestFocus();
222  base::MessageLoop::current()->RunUntilIdle();
223
224  // Change the accessible name of button3.
225  button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
226
227  // Advance focus to the next button and test that we got the
228  // expected notification with the name of button 2.
229  views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
230  focus_event_count_ = 0;
231  focus_manager->AdvanceFocus(false);
232  base::MessageLoop::current()->RunUntilIdle();
233  EXPECT_EQ(1, focus_event_count_);
234  EXPECT_EQ(kButton2ASCII, last_control_name_);
235
236  // Advance to button 3. Expect the new accessible name we assigned.
237  focus_manager->AdvanceFocus(false);
238  base::MessageLoop::current()->RunUntilIdle();
239  EXPECT_EQ(2, focus_event_count_);
240  EXPECT_EQ(kButton3NewASCII, last_control_name_);
241
242  // Advance to button 1 and check the notification.
243  focus_manager->AdvanceFocus(false);
244  base::MessageLoop::current()->RunUntilIdle();
245  EXPECT_EQ(3, focus_event_count_);
246  EXPECT_EQ(kButton1ASCII, last_control_name_);
247
248  window->CloseNow();
249}
250
251TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
252  const char kToolbarNameASCII[] = "MyToolbar";
253  const char kButtonNameASCII[] = "MyButton";
254
255  // Create a toolbar with a button.
256  views::View* contents = new ViewWithNameAndRole(
257      ASCIIToUTF16(kToolbarNameASCII),
258      ui::AccessibilityTypes::ROLE_TOOLBAR);
259  views::LabelButton* button = new views::LabelButton(
260      NULL, ASCIIToUTF16(kButtonNameASCII));
261  button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
262  contents->AddChildView(button);
263
264  // Put the view in a window.
265  views::Widget* window = CreateWindowWithContents(contents);
266
267  // Set focus to the button.
268  focus_event_count_ = 0;
269  button->RequestFocus();
270
271  base::MessageLoop::current()->RunUntilIdle();
272
273  // Test that we got the event with the expected name and context.
274  EXPECT_EQ(kInitialFocusCount, focus_event_count_);
275  EXPECT_EQ(kButtonNameASCII, last_control_name_);
276  EXPECT_EQ(kToolbarNameASCII, last_control_context_);
277
278  window->CloseNow();
279}
280
281TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
282  const char kAlertTextASCII[] = "MyAlertText";
283  const char kButtonNameASCII[] = "MyButton";
284
285  // Create an alert with static text and a button, similar to an infobar.
286  views::View* contents = new ViewWithNameAndRole(
287      string16(),
288      ui::AccessibilityTypes::ROLE_ALERT);
289  views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
290  contents->AddChildView(label);
291  views::LabelButton* button = new views::LabelButton(
292      NULL, ASCIIToUTF16(kButtonNameASCII));
293  button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
294  contents->AddChildView(button);
295
296  // Put the view in a window.
297  views::Widget* window = CreateWindowWithContents(contents);
298
299  // Set focus to the button.
300  focus_event_count_ = 0;
301  button->RequestFocus();
302
303  base::MessageLoop::current()->RunUntilIdle();
304
305  // Test that we got the event with the expected name and context.
306  EXPECT_EQ(kInitialFocusCount, focus_event_count_);
307  EXPECT_EQ(kButtonNameASCII, last_control_name_);
308  EXPECT_EQ(kAlertTextASCII, last_control_context_);
309
310  window->CloseNow();
311}
312
313TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
314  const char kContentsNameASCII[] = "Contents";
315  const char kOldNameASCII[] = "OldName";
316  const char kNewNameASCII[] = "NewName";
317
318  // Create a toolbar with a button.
319  views::View* contents = new ViewWithNameAndRole(
320      ASCIIToUTF16(kContentsNameASCII),
321      ui::AccessibilityTypes::ROLE_CLIENT);
322  ViewWithNameAndRole* child = new ViewWithNameAndRole(
323      ASCIIToUTF16(kOldNameASCII),
324      ui::AccessibilityTypes::ROLE_PUSHBUTTON);
325  child->set_focusable(true);
326  contents->AddChildView(child);
327
328  // Put the view in a window.
329  views::Widget* window = CreateWindowWithContents(contents);
330
331  // Set focus to the child view.
332  focus_event_count_ = 0;
333  child->RequestFocus();
334
335  // Change the child's name after the focus notification.
336  child->set_name(ASCIIToUTF16(kNewNameASCII));
337
338  // We shouldn't get the notification right away.
339  EXPECT_EQ(0, focus_event_count_);
340
341  // Process anything in the event loop. Now we should get the notification,
342  // and it should give us the new control name, not the old one.
343  base::MessageLoop::current()->RunUntilIdle();
344  EXPECT_EQ(kInitialFocusCount, focus_event_count_);
345  EXPECT_EQ(kNewNameASCII, last_control_name_);
346
347  window->CloseNow();
348}
349
350TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
351  const char kContentsNameASCII[] = "Contents";
352  const char kNameASCII[] = "OldName";
353
354  // Create a toolbar with a button.
355  views::View* contents = new ViewWithNameAndRole(
356      ASCIIToUTF16(kContentsNameASCII),
357      ui::AccessibilityTypes::ROLE_CLIENT);
358  ViewWithNameAndRole* child = new ViewWithNameAndRole(
359      ASCIIToUTF16(kNameASCII),
360      ui::AccessibilityTypes::ROLE_PUSHBUTTON);
361  child->set_focusable(true);
362  contents->AddChildView(child);
363
364  // Put the view in a window.
365  views::Widget* window = CreateWindowWithContents(contents);
366
367  // Set focus to the child view.
368  focus_event_count_ = 0;
369  child->RequestFocus();
370
371  // Delete the child!
372  delete child;
373
374  // We shouldn't get the notification right away.
375  EXPECT_EQ(0, focus_event_count_);
376
377  // Process anything in the event loop. We shouldn't get a notification
378  // because the view is no longer valid, and this shouldn't crash.
379  base::MessageLoop::current()->RunUntilIdle();
380  EXPECT_EQ(0, focus_event_count_);
381
382  window->CloseNow();
383}
384