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