blob: 1559a29f9e8b8f851114b2710839239a02ce29d1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/accessibility/accessibility_extension_api.h"
#include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
#include "chrome/test/base/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/accessibility/accessible_view_state.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/views/widget/native_widget.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#if defined(OS_WIN)
#include "ui/base/win/scoped_ole_initializer.h"
#endif
#if defined(USE_AURA)
#include "ui/aura/root_window.h"
#include "ui/aura/test/aura_test_helper.h"
#endif
namespace {
// The expected initial focus count.
#if defined(OS_WIN) && !defined(USE_AURA)
// On windows (non-aura) this code triggers activating the window. Activating
// the window triggers clearing the focus then resetting it. This results in an
// additional focus change.
const int kInitialFocusCount = 2;
#else
const int kInitialFocusCount = 1;
#endif
} // namespace
class AccessibilityViewsDelegate : public views::TestViewsDelegate {
public:
AccessibilityViewsDelegate() {}
virtual ~AccessibilityViewsDelegate() {}
// Overridden from views::TestViewsDelegate:
virtual void NotifyAccessibilityEvent(
views::View* view, ui::AccessibilityTypes::Event event_type) OVERRIDE {
AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
view, event_type);
}
DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
};
class AccessibilityWindowDelegate : public views::WidgetDelegate {
public:
explicit AccessibilityWindowDelegate(views::View* contents)
: contents_(contents) { }
// Overridden from views::WidgetDelegate:
virtual void DeleteDelegate() OVERRIDE { delete this; }
virtual views::View* GetContentsView() OVERRIDE { return contents_; }
virtual const views::Widget* GetWidget() const OVERRIDE {
return contents_->GetWidget();
}
virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
private:
views::View* contents_;
DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
};
class ViewWithNameAndRole : public views::View {
public:
explicit ViewWithNameAndRole(const string16& name,
ui::AccessibilityTypes::Role role)
: name_(name),
role_(role) {
}
virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
views::View::GetAccessibleState(state);
state->name = name_;
state->role = role_;
}
void set_name(const string16& name) { name_ = name; }
private:
string16 name_;
ui::AccessibilityTypes::Role role_;
DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
};
class AccessibilityEventRouterViewsTest
: public testing::Test {
public:
AccessibilityEventRouterViewsTest() : focus_event_count_(0) {
}
virtual void SetUp() {
#if defined(OS_WIN)
ole_initializer_.reset(new ui::ScopedOleInitializer());
#endif
views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
#if defined(USE_AURA)
aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
aura_test_helper_->SetUp();
#endif // USE_AURA
EnableAccessibilityAndListenToFocusNotifications();
}
virtual void TearDown() {
ClearCallback();
#if defined(USE_AURA)
aura_test_helper_->TearDown();
#endif
delete views::ViewsDelegate::views_delegate;
views::ViewsDelegate::views_delegate = NULL;
// The Widget's FocusManager is deleted using DeleteSoon - this
// forces it to be deleted now, so we don't have any memory leaks
// when this method exits.
base::MessageLoop::current()->RunUntilIdle();
#if defined(OS_WIN)
ole_initializer_.reset();
#endif
}
views::Widget* CreateWindowWithContents(views::View* contents) {
gfx::NativeView context = NULL;
#if defined(USE_AURA)
context = aura_test_helper_->root_window();
#endif
views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
new AccessibilityWindowDelegate(contents),
context,
gfx::Rect(0, 0, 500, 500));
// Create a profile and associate it with this window.
widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
return widget;
}
void EnableAccessibilityAndListenToFocusNotifications() {
// Switch on accessibility event notifications.
ExtensionAccessibilityEventRouter* accessibility_event_router =
ExtensionAccessibilityEventRouter::GetInstance();
accessibility_event_router->SetAccessibilityEnabled(true);
accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
&AccessibilityEventRouterViewsTest::OnFocusEvent,
base::Unretained(this)));
}
void ClearCallback() {
ExtensionAccessibilityEventRouter* accessibility_event_router =
ExtensionAccessibilityEventRouter::GetInstance();
accessibility_event_router->ClearControlEventCallback();
}
protected:
// Handle Focus event.
virtual void OnFocusEvent(ui::AccessibilityTypes::Event event,
const AccessibilityControlInfo* info) {
focus_event_count_++;
last_control_name_ = info->name();
last_control_context_ = info->context();
}
base::MessageLoopForUI message_loop_;
int focus_event_count_;
std::string last_control_name_;
std::string last_control_context_;
TestingProfile profile_;
#if defined(OS_WIN)
scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
#endif
#if defined(USE_AURA)
scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
#endif
};
TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
const char kButton1ASCII[] = "Button1";
const char kButton2ASCII[] = "Button2";
const char kButton3ASCII[] = "Button3";
const char kButton3NewASCII[] = "Button3New";
// Create a contents view with 3 buttons.
views::View* contents = new views::View();
views::LabelButton* button1 = new views::LabelButton(
NULL, ASCIIToUTF16(kButton1ASCII));
button1->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
contents->AddChildView(button1);
views::LabelButton* button2 = new views::LabelButton(
NULL, ASCIIToUTF16(kButton2ASCII));
button2->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
contents->AddChildView(button2);
views::LabelButton* button3 = new views::LabelButton(
NULL, ASCIIToUTF16(kButton3ASCII));
button3->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
contents->AddChildView(button3);
// Put the view in a window.
views::Widget* window = CreateWindowWithContents(contents);
window->Show();
window->Activate();
// Set focus to the first button initially and run message loop to execute
// callback.
button1->RequestFocus();
base::MessageLoop::current()->RunUntilIdle();
// Change the accessible name of button3.
button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
// Advance focus to the next button and test that we got the
// expected notification with the name of button 2.
views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
focus_event_count_ = 0;
focus_manager->AdvanceFocus(false);
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(1, focus_event_count_);
EXPECT_EQ(kButton2ASCII, last_control_name_);
// Advance to button 3. Expect the new accessible name we assigned.
focus_manager->AdvanceFocus(false);
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(2, focus_event_count_);
EXPECT_EQ(kButton3NewASCII, last_control_name_);
// Advance to button 1 and check the notification.
focus_manager->AdvanceFocus(false);
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(3, focus_event_count_);
EXPECT_EQ(kButton1ASCII, last_control_name_);
window->CloseNow();
}
TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
const char kToolbarNameASCII[] = "MyToolbar";
const char kButtonNameASCII[] = "MyButton";
// Create a toolbar with a button.
views::View* contents = new ViewWithNameAndRole(
ASCIIToUTF16(kToolbarNameASCII),
ui::AccessibilityTypes::ROLE_TOOLBAR);
views::LabelButton* button = new views::LabelButton(
NULL, ASCIIToUTF16(kButtonNameASCII));
button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
contents->AddChildView(button);
// Put the view in a window.
views::Widget* window = CreateWindowWithContents(contents);
// Set focus to the button.
focus_event_count_ = 0;
button->RequestFocus();
base::MessageLoop::current()->RunUntilIdle();
// Test that we got the event with the expected name and context.
EXPECT_EQ(kInitialFocusCount, focus_event_count_);
EXPECT_EQ(kButtonNameASCII, last_control_name_);
EXPECT_EQ(kToolbarNameASCII, last_control_context_);
window->CloseNow();
}
TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
const char kAlertTextASCII[] = "MyAlertText";
const char kButtonNameASCII[] = "MyButton";
// Create an alert with static text and a button, similar to an infobar.
views::View* contents = new ViewWithNameAndRole(
string16(),
ui::AccessibilityTypes::ROLE_ALERT);
views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
contents->AddChildView(label);
views::LabelButton* button = new views::LabelButton(
NULL, ASCIIToUTF16(kButtonNameASCII));
button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
contents->AddChildView(button);
// Put the view in a window.
views::Widget* window = CreateWindowWithContents(contents);
// Set focus to the button.
focus_event_count_ = 0;
button->RequestFocus();
base::MessageLoop::current()->RunUntilIdle();
// Test that we got the event with the expected name and context.
EXPECT_EQ(kInitialFocusCount, focus_event_count_);
EXPECT_EQ(kButtonNameASCII, last_control_name_);
EXPECT_EQ(kAlertTextASCII, last_control_context_);
window->CloseNow();
}
TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
const char kContentsNameASCII[] = "Contents";
const char kOldNameASCII[] = "OldName";
const char kNewNameASCII[] = "NewName";
// Create a toolbar with a button.
views::View* contents = new ViewWithNameAndRole(
ASCIIToUTF16(kContentsNameASCII),
ui::AccessibilityTypes::ROLE_CLIENT);
ViewWithNameAndRole* child = new ViewWithNameAndRole(
ASCIIToUTF16(kOldNameASCII),
ui::AccessibilityTypes::ROLE_PUSHBUTTON);
child->set_focusable(true);
contents->AddChildView(child);
// Put the view in a window.
views::Widget* window = CreateWindowWithContents(contents);
// Set focus to the child view.
focus_event_count_ = 0;
child->RequestFocus();
// Change the child's name after the focus notification.
child->set_name(ASCIIToUTF16(kNewNameASCII));
// We shouldn't get the notification right away.
EXPECT_EQ(0, focus_event_count_);
// Process anything in the event loop. Now we should get the notification,
// and it should give us the new control name, not the old one.
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(kInitialFocusCount, focus_event_count_);
EXPECT_EQ(kNewNameASCII, last_control_name_);
window->CloseNow();
}
TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
const char kContentsNameASCII[] = "Contents";
const char kNameASCII[] = "OldName";
// Create a toolbar with a button.
views::View* contents = new ViewWithNameAndRole(
ASCIIToUTF16(kContentsNameASCII),
ui::AccessibilityTypes::ROLE_CLIENT);
ViewWithNameAndRole* child = new ViewWithNameAndRole(
ASCIIToUTF16(kNameASCII),
ui::AccessibilityTypes::ROLE_PUSHBUTTON);
child->set_focusable(true);
contents->AddChildView(child);
// Put the view in a window.
views::Widget* window = CreateWindowWithContents(contents);
// Set focus to the child view.
focus_event_count_ = 0;
child->RequestFocus();
// Delete the child!
delete child;
// We shouldn't get the notification right away.
EXPECT_EQ(0, focus_event_count_);
// Process anything in the event loop. We shouldn't get a notification
// because the view is no longer valid, and this shouldn't crash.
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(0, focus_event_count_);
window->CloseNow();
}