1ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com// Use of this source code is governed by a BSD-style license that can be
3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com// found in the LICENSE file.
4ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#include <string>
6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
7ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "base/message_loop/message_loop.h"
8ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "base/strings/string_util.h"
9ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "base/strings/utf_string_conversions.h"
10ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "chrome/browser/accessibility/accessibility_extension_api.h"
11ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "chrome/browser/accessibility/accessibility_extension_api_constants.h"
127d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
13ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "chrome/test/base/testing_profile.h"
14a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.org#include "testing/gtest/include/gtest/gtest.h"
15c44be0e9e4cee5402909c06370a630eee188a8f3bsalomon#include "ui/accessibility/ax_enums.h"
16c44be0e9e4cee5402909c06370a630eee188a8f3bsalomon#include "ui/accessibility/ax_view_state.h"
17ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "ui/base/models/simple_menu_model.h"
18ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "ui/views/controls/button/label_button.h"
19ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#include "ui/views/controls/label.h"
207d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/controls/menu/menu_item_view.h"
217d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/controls/menu/menu_model_adapter.h"
227d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/controls/menu/menu_runner.h"
237d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/controls/menu/submenu_view.h"
247d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/layout/grid_layout.h"
257d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/test/test_views_delegate.h"
261d86ee8363018d71245d23573619473ae7e7d1c9robertphillips#include "ui/views/widget/native_widget.h"
277d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/widget/root_view.h"
287d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/widget/widget.h"
297d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/views/widget/widget_delegate.h"
301d86ee8363018d71245d23573619473ae7e7d1c9robertphillips
317d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#if defined(OS_WIN)
327d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#include "ui/base/win/scoped_ole_initializer.h"
33ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#endif
34c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org
35ade9a3485e78d471f5f0902e9e50a2ec74c88e76skia.committer@gmail.com#if defined(USE_AURA)
3617dabfc38d5ecba73845fa11e3cb3a617f52ead0robertphillips#include "ui/aura/test/aura_test_helper.h"
3717dabfc38d5ecba73845fa11e3cb3a617f52ead0robertphillips#include "ui/aura/window_event_dispatcher.h"
3817dabfc38d5ecba73845fa11e3cb3a617f52ead0robertphillips#include "ui/compositor/test/context_factories_for_test.h"
3917dabfc38d5ecba73845fa11e3cb3a617f52ead0robertphillips#include "ui/wm/core/default_activation_client.h"
40ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com#endif
41ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com
42d537341e16524d1e22ac5e6c8b9c8f274ba1833crobertphillipsusing base::ASCIIToUTF16;
43ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com
44a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.orgclass AccessibilityViewsDelegate : public views::TestViewsDelegate {
45a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.org public:
46ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com  AccessibilityViewsDelegate() {}
477801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org  virtual ~AccessibilityViewsDelegate() {}
487801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org
49c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org  // Overridden from views::TestViewsDelegate:
50c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org  virtual void NotifyAccessibilityEvent(
51ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com      views::View* view, ui::AXEvent event_type) OVERRIDE {
527d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org    AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
537d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org        view, event_type);
5417dabfc38d5ecba73845fa11e3cb3a617f52ead0robertphillips  }
557801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org
56ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com private:
5767ed64e9aa70f5a95a2d309f9b73dc0009f3ed8ccommit-bot@chromium.org  DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
58a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.org};
59a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.org
6017dabfc38d5ecba73845fa11e3cb3a617f52ead0robertphillipsclass AccessibilityWindowDelegate : public views::WidgetDelegate {
617801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org public:
62a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.org  explicit AccessibilityWindowDelegate(views::View* contents)
63a8916ffd90c04dc6cc1fb9ba94af2ff950284fadcommit-bot@chromium.org      : contents_(contents) { }
641d86ee8363018d71245d23573619473ae7e7d1c9robertphillips
65d537341e16524d1e22ac5e6c8b9c8f274ba1833crobertphillips  // Overridden from views::WidgetDelegate:
668b169311b59ab84e8ca6f3630a1e960cc1be751erobertphillips@google.com  virtual void DeleteDelegate() OVERRIDE { delete this; }
677801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org  virtual views::View* GetContentsView() OVERRIDE { return contents_; }
687801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org  virtual const views::Widget* GetWidget() const OVERRIDE {
697801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org    return contents_->GetWidget();
70ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com  }
711d86ee8363018d71245d23573619473ae7e7d1c9robertphillips  virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
72ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com
73ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com private:
74c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org  views::View* contents_;
75c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org
761d86ee8363018d71245d23573619473ae7e7d1c9robertphillips  DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
77ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com};
781d86ee8363018d71245d23573619473ae7e7d1c9robertphillips
791d86ee8363018d71245d23573619473ae7e7d1c9robertphillipsclass ViewWithNameAndRole : public views::View {
801d86ee8363018d71245d23573619473ae7e7d1c9robertphillips public:
811d86ee8363018d71245d23573619473ae7e7d1c9robertphillips  explicit ViewWithNameAndRole(const base::string16& name,
821d86ee8363018d71245d23573619473ae7e7d1c9robertphillips                               ui::AXRole role)
831d86ee8363018d71245d23573619473ae7e7d1c9robertphillips      : name_(name),
84320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips        role_(role) {
85320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips  }
86320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips
87320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips  virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
88320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    views::View::GetAccessibleState(state);
89320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    state->name = name_;
901d86ee8363018d71245d23573619473ae7e7d1c9robertphillips    state->role = role_;
911d86ee8363018d71245d23573619473ae7e7d1c9robertphillips  }
921d86ee8363018d71245d23573619473ae7e7d1c9robertphillips
931d86ee8363018d71245d23573619473ae7e7d1c9robertphillips  void set_name(const base::string16& name) { name_ = name; }
941d86ee8363018d71245d23573619473ae7e7d1c9robertphillips
951d86ee8363018d71245d23573619473ae7e7d1c9robertphillips private:
96952841bf41a81228c23d16c7204b458abe0d7136robertphillips  base::string16 name_;
97952841bf41a81228c23d16c7204b458abe0d7136robertphillips  ui::AXRole role_;
981d86ee8363018d71245d23573619473ae7e7d1c9robertphillips  DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
991d86ee8363018d71245d23573619473ae7e7d1c9robertphillips};
100ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com
101952841bf41a81228c23d16c7204b458abe0d7136robertphillipsclass AccessibilityEventRouterViewsTest
102952841bf41a81228c23d16c7204b458abe0d7136robertphillips    : public testing::Test {
103952841bf41a81228c23d16c7204b458abe0d7136robertphillips public:
104952841bf41a81228c23d16c7204b458abe0d7136robertphillips  AccessibilityEventRouterViewsTest() : control_event_count_(0) {
105952841bf41a81228c23d16c7204b458abe0d7136robertphillips  }
106952841bf41a81228c23d16c7204b458abe0d7136robertphillips
10750df4d013f840749f70d1759c23c4217e727fd54skia.committer@gmail.com  virtual void SetUp() {
108c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org#if defined(OS_WIN)
109c4f30b1074e1068d6fc0f368428f410ed6d4b2b5robertphillips    ole_initializer_.reset(new ui::ScopedOleInitializer());
11050df4d013f840749f70d1759c23c4217e727fd54skia.committer@gmail.com#endif
111c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org    views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
112c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org#if defined(USE_AURA)
113c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org    // The ContextFactory must exist before any Compositors are created.
11450df4d013f840749f70d1759c23c4217e727fd54skia.committer@gmail.com    bool enable_pixel_output = false;
1153fddf0eed6dc2873bcc8e584f435c6cd34964518commit-bot@chromium.org    ui::ContextFactory* context_factory =
1163fddf0eed6dc2873bcc8e584f435c6cd34964518commit-bot@chromium.org        ui::InitializeContextFactoryForTests(enable_pixel_output);
117759c16e20dc42577226c8805bfea92d8bacb14d8reed@google.com
118ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com    aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
1197801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org    aura_test_helper_->SetUp(context_factory);
1207801faaab9bf7dd0ac67e859c4e284e74f7bd46fcommit-bot@chromium.org    new wm::DefaultActivationClient(aura_test_helper_->root_window());
121320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips#endif  // USE_AURA
122320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    EnableAccessibilityAndListenToFocusNotifications();
123320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips  }
124320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips
125320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips  virtual void TearDown() {
126320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    ClearCallback();
127320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips#if defined(USE_AURA)
128320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    aura_test_helper_->TearDown();
129320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    ui::TerminateContextFactoryForTests();
130320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips#endif
131320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips    delete views::ViewsDelegate::views_delegate;
132320c92380fe6a43dffbcd3f9e7c99897da44298drobertphillips
133ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com    // The Widget's FocusManager is deleted using DeleteSoon - this
1341d86ee8363018d71245d23573619473ae7e7d1c9robertphillips    // forces it to be deleted now, so we don't have any memory leaks
13550df4d013f840749f70d1759c23c4217e727fd54skia.committer@gmail.com    // when this method exits.
136952841bf41a81228c23d16c7204b458abe0d7136robertphillips    base::MessageLoop::current()->RunUntilIdle();
137952841bf41a81228c23d16c7204b458abe0d7136robertphillips
138952841bf41a81228c23d16c7204b458abe0d7136robertphillips#if defined(OS_WIN)
139952841bf41a81228c23d16c7204b458abe0d7136robertphillips    ole_initializer_.reset();
140952841bf41a81228c23d16c7204b458abe0d7136robertphillips#endif
141952841bf41a81228c23d16c7204b458abe0d7136robertphillips  }
142952841bf41a81228c23d16c7204b458abe0d7136robertphillips
143952841bf41a81228c23d16c7204b458abe0d7136robertphillips  views::Widget* CreateWindowWithContents(views::View* contents) {
14450df4d013f840749f70d1759c23c4217e727fd54skia.committer@gmail.com    gfx::NativeWindow context = NULL;
1457d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org#if defined(USE_AURA)
146c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org    context = aura_test_helper_->root_window();
1471d86ee8363018d71245d23573619473ae7e7d1c9robertphillips#endif
148c9b2c885be8d2bb39f1d75cc316278fa8d0fa9f0commit-bot@chromium.org    views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
1497d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org        new AccessibilityWindowDelegate(contents),
1507d330eb19cd3c9278abce68ca0e3efabf2ec8f87commit-bot@chromium.org        context,
151ac10a2d039c5d52eed66e27cbbc503ab523c1cd5reed@google.com        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
427TEST_F(AccessibilityEventRouterViewsTest, AccessibilityFocusableView) {
428  // Create a view with a child view.
429  views::View* parent = new views::View();
430  views::View* child = new views::View();
431  parent->AddChildView(child);
432
433  // Put the view in a window.
434  views::Widget* window = CreateWindowWithContents(parent);
435
436  // Since the child view has no accessibility focusable ancestors, this
437  // should still be the child view.
438  views::View* accessible_view =
439      AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child);
440  EXPECT_EQ(accessible_view, child);
441
442  // Now make the parent view accessibility focusable. Calling
443  // FindFirstAccessibleAncestor() again on child should return the parent
444  // view.
445  parent->SetAccessibilityFocusable(true);
446  accessible_view =
447      AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child);
448  EXPECT_EQ(accessible_view, parent);
449
450  window->CloseNow();
451}
452
453namespace {
454
455class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
456 public:
457  enum {
458    IDC_MENU_ITEM_1,
459    IDC_MENU_ITEM_2,
460    IDC_MENU_INVISIBLE,
461    IDC_MENU_ITEM_3,
462  };
463
464  SimpleMenuDelegate() {}
465  virtual ~SimpleMenuDelegate() {}
466
467  views::MenuItemView* BuildMenu() {
468    menu_model_.reset(new ui::SimpleMenuModel(this));
469    menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
470    menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
471    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
472    menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
473    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
474    menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));
475
476    menu_adapter_.reset(new views::MenuModelAdapter(menu_model_.get()));
477    views::MenuItemView* menu_view = menu_adapter_->CreateMenu();
478    menu_runner_.reset(new views::MenuRunner(menu_view, 0));
479    return menu_view;
480  }
481
482  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
483    return false;
484  }
485
486  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
487    return true;
488  }
489
490  virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
491    return command_id != IDC_MENU_INVISIBLE;
492  }
493
494  virtual bool GetAcceleratorForCommandId(
495      int command_id,
496      ui::Accelerator* accelerator) OVERRIDE {
497    return false;
498  }
499
500  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
501  }
502
503 private:
504  scoped_ptr<ui::SimpleMenuModel> menu_model_;
505  scoped_ptr<views::MenuModelAdapter> menu_adapter_;
506  scoped_ptr<views::MenuRunner> menu_runner_;
507
508  DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
509};
510
511}  // namespace
512
513TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
514  SimpleMenuDelegate menu_delegate;
515  views::MenuItemView* menu = menu_delegate.BuildMenu();
516  views::View* menu_container = menu->CreateSubmenu();
517
518  struct TestCase {
519    int command_id;
520    int expected_index;
521    int expected_count;
522  } kTestCases[] = {
523    { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
524    { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
525    { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
526    { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
527  };
528
529  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
530    int index = 0;
531    int count = 0;
532
533    AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
534        menu_container,
535        menu->GetMenuItemByID(kTestCases[i].command_id),
536        &index,
537        &count);
538    EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
539    EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;
540  }
541}
542