blob: b4438b0aa46f31efc8775d4fcdd9c35bc7802ef3 [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 "ui/views/focus/focus_manager.h"
#include <stddef.h>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/icu_test_util.h"
#include "build/build_config.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/test_accelerator_target.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/accessible_pane_view.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/buildflags.h"
#include "ui/views/focus/focus_manager_delegate.h"
#include "ui/views/focus/focus_manager_factory.h"
#include "ui/views/focus/widget_focus_manager.h"
#include "ui/views/test/focus_manager_test.h"
#include "ui/views/test/native_widget_factory.h"
#include "ui/views/test/test_platform_native_widget.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/aura/client/focus_client.h"
#include "ui/views/widget/native_widget_aura.h"
#endif // USE_AURA
namespace views {
enum FocusTestEventType { ON_FOCUS = 0, ON_BLUR };
struct FocusTestEvent {
FocusTestEventType type;
int view_id;
FocusManager::FocusChangeReason focus_change_reason;
};
class SimpleTestView : public View {
public:
SimpleTestView(std::vector<FocusTestEvent>* event_list, int view_id)
: event_list_(event_list) {
SetFocusBehavior(FocusBehavior::ALWAYS);
SetID(view_id);
}
SimpleTestView(const SimpleTestView&) = delete;
SimpleTestView& operator=(const SimpleTestView&) = delete;
void OnFocus() override {
event_list_->push_back({
ON_FOCUS,
GetID(),
GetFocusManager()->focus_change_reason(),
});
}
void OnBlur() override {
event_list_->push_back({
ON_BLUR,
GetID(),
GetFocusManager()->focus_change_reason(),
});
}
private:
std::vector<FocusTestEvent>* event_list_;
};
// Tests that the appropriate Focus related methods are called when a View
// gets/loses focus.
TEST_F(FocusManagerTest, ViewFocusCallbacks) {
std::vector<FocusTestEvent> event_list;
const int kView1ID = 1;
const int kView2ID = 2;
SimpleTestView* view1 = new SimpleTestView(&event_list, kView1ID);
SimpleTestView* view2 = new SimpleTestView(&event_list, kView2ID);
GetContentsView()->AddChildView(view1);
GetContentsView()->AddChildView(view2);
view1->RequestFocus();
ASSERT_EQ(1, static_cast<int>(event_list.size()));
EXPECT_EQ(ON_FOCUS, event_list[0].type);
EXPECT_EQ(kView1ID, event_list[0].view_id);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[0].focus_change_reason);
event_list.clear();
view2->RequestFocus();
ASSERT_EQ(2, static_cast<int>(event_list.size()));
EXPECT_EQ(ON_BLUR, event_list[0].type);
EXPECT_EQ(kView1ID, event_list[0].view_id);
EXPECT_EQ(ON_FOCUS, event_list[1].type);
EXPECT_EQ(kView2ID, event_list[1].view_id);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[0].focus_change_reason);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[1].focus_change_reason);
event_list.clear();
GetFocusManager()->ClearFocus();
ASSERT_EQ(1, static_cast<int>(event_list.size()));
EXPECT_EQ(ON_BLUR, event_list[0].type);
EXPECT_EQ(kView2ID, event_list[0].view_id);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[0].focus_change_reason);
}
TEST_F(FocusManagerTest, FocusChangeListener) {
View* view1 = new View();
view1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
View* view2 = new View();
view2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(view1);
GetContentsView()->AddChildView(view2);
TestFocusChangeListener listener;
AddFocusChangeListener(&listener);
// Required for VS2010:
// http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
views::View* null_view = nullptr;
view1->RequestFocus();
ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(null_view, view1));
listener.ClearFocusChanges();
view2->RequestFocus();
ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view1, view2));
listener.ClearFocusChanges();
GetFocusManager()->ClearFocus();
ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view2, null_view));
}
TEST_F(FocusManagerTest, WidgetFocusChangeListener) {
// First, ensure the simulator is aware of the Widget created in SetUp() being
// currently active.
test::WidgetTest::SimulateNativeActivate(GetWidget());
TestWidgetFocusChangeListener widget_listener;
AddWidgetFocusChangeListener(&widget_listener);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(10, 10, 100, 100);
params.parent = GetWidget()->GetNativeView();
std::unique_ptr<Widget> widget1(new Widget);
widget1->Init(std::move(params));
widget1->Show();
std::unique_ptr<Widget> widget2(new Widget);
widget2->Init(std::move(params));
widget2->Show();
widget_listener.ClearFocusChanges();
gfx::NativeView native_view1 = widget1->GetNativeView();
test::WidgetTest::SimulateNativeActivate(widget1.get());
ASSERT_EQ(2u, widget_listener.focus_changes().size());
EXPECT_EQ(gfx::kNullNativeView, widget_listener.focus_changes()[0]);
EXPECT_EQ(native_view1, widget_listener.focus_changes()[1]);
widget_listener.ClearFocusChanges();
gfx::NativeView native_view2 = widget2->GetNativeView();
test::WidgetTest::SimulateNativeActivate(widget2.get());
ASSERT_EQ(2u, widget_listener.focus_changes().size());
EXPECT_EQ(gfx::kNullNativeView, widget_listener.focus_changes()[0]);
EXPECT_EQ(native_view2, widget_listener.focus_changes()[1]);
}
TEST_F(FocusManagerTest, CallsNormalAcceleratorTarget) {
FocusManager* focus_manager = GetFocusManager();
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
ui::Accelerator escape_accelerator(ui::VKEY_ESCAPE, ui::EF_NONE);
ui::TestAcceleratorTarget return_target(true);
ui::TestAcceleratorTarget escape_target(true);
EXPECT_EQ(return_target.accelerator_count(), 0);
EXPECT_EQ(escape_target.accelerator_count(), 0);
// Register targets.
focus_manager->RegisterAccelerator(return_accelerator,
ui::AcceleratorManager::kNormalPriority,
&return_target);
focus_manager->RegisterAccelerator(escape_accelerator,
ui::AcceleratorManager::kNormalPriority,
&escape_target);
// Hitting the return key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(return_target.accelerator_count(), 1);
EXPECT_EQ(escape_target.accelerator_count(), 0);
// Hitting the escape key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(return_target.accelerator_count(), 1);
EXPECT_EQ(escape_target.accelerator_count(), 1);
// Register another target for the return key.
ui::TestAcceleratorTarget return_target2(true);
EXPECT_EQ(return_target2.accelerator_count(), 0);
focus_manager->RegisterAccelerator(return_accelerator,
ui::AcceleratorManager::kNormalPriority,
&return_target2);
// Hitting the return key; return_target2 has the priority.
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(return_target.accelerator_count(), 1);
EXPECT_EQ(return_target2.accelerator_count(), 1);
// Register a target that does not process the accelerator event.
ui::TestAcceleratorTarget return_target3(false);
EXPECT_EQ(return_target3.accelerator_count(), 0);
focus_manager->RegisterAccelerator(return_accelerator,
ui::AcceleratorManager::kNormalPriority,
&return_target3);
// Hitting the return key.
// Since the event handler of return_target3 returns false, return_target2
// should be called too.
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(return_target.accelerator_count(), 1);
EXPECT_EQ(return_target2.accelerator_count(), 2);
EXPECT_EQ(return_target3.accelerator_count(), 1);
// Unregister return_target2.
focus_manager->UnregisterAccelerator(return_accelerator, &return_target2);
// Hitting the return key. return_target3 and return_target should be called.
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(return_target.accelerator_count(), 2);
EXPECT_EQ(return_target2.accelerator_count(), 2);
EXPECT_EQ(return_target3.accelerator_count(), 2);
// Unregister targets.
focus_manager->UnregisterAccelerator(return_accelerator, &return_target);
focus_manager->UnregisterAccelerator(return_accelerator, &return_target3);
focus_manager->UnregisterAccelerator(escape_accelerator, &escape_target);
// Hitting the return key and the escape key. Nothing should happen.
EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(return_target.accelerator_count(), 2);
EXPECT_EQ(return_target2.accelerator_count(), 2);
EXPECT_EQ(return_target3.accelerator_count(), 2);
EXPECT_FALSE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target.accelerator_count(), 1);
}
TEST_F(FocusManagerTest, HighPriorityHandlers) {
FocusManager* focus_manager = GetFocusManager();
ui::Accelerator escape_accelerator(ui::VKEY_ESCAPE, ui::EF_NONE);
ui::TestAcceleratorTarget escape_target_high(true);
ui::TestAcceleratorTarget escape_target_normal(true);
EXPECT_EQ(escape_target_high.accelerator_count(), 0);
EXPECT_EQ(escape_target_normal.accelerator_count(), 0);
EXPECT_FALSE(focus_manager->HasPriorityHandler(escape_accelerator));
// Register high priority target.
focus_manager->RegisterAccelerator(escape_accelerator,
ui::AcceleratorManager::kHighPriority,
&escape_target_high);
EXPECT_TRUE(focus_manager->HasPriorityHandler(escape_accelerator));
// Hit the escape key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target_high.accelerator_count(), 1);
EXPECT_EQ(escape_target_normal.accelerator_count(), 0);
// Add a normal priority target and make sure it doesn't see the key.
focus_manager->RegisterAccelerator(escape_accelerator,
ui::AcceleratorManager::kNormalPriority,
&escape_target_normal);
// Checks if the correct target is registered (same as before, the high
// priority one).
EXPECT_TRUE(focus_manager->HasPriorityHandler(escape_accelerator));
// Hit the escape key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target_high.accelerator_count(), 2);
EXPECT_EQ(escape_target_normal.accelerator_count(), 0);
// Unregister the high priority accelerator.
focus_manager->UnregisterAccelerator(escape_accelerator, &escape_target_high);
EXPECT_FALSE(focus_manager->HasPriorityHandler(escape_accelerator));
// Hit the escape key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target_high.accelerator_count(), 2);
EXPECT_EQ(escape_target_normal.accelerator_count(), 1);
// Add the high priority target back and make sure it starts seeing the key.
focus_manager->RegisterAccelerator(escape_accelerator,
ui::AcceleratorManager::kHighPriority,
&escape_target_high);
EXPECT_TRUE(focus_manager->HasPriorityHandler(escape_accelerator));
// Hit the escape key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target_high.accelerator_count(), 3);
EXPECT_EQ(escape_target_normal.accelerator_count(), 1);
// Unregister the normal priority accelerator.
focus_manager->UnregisterAccelerator(escape_accelerator,
&escape_target_normal);
EXPECT_TRUE(focus_manager->HasPriorityHandler(escape_accelerator));
// Hit the escape key.
EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target_high.accelerator_count(), 4);
EXPECT_EQ(escape_target_normal.accelerator_count(), 1);
// Unregister the high priority accelerator.
focus_manager->UnregisterAccelerator(escape_accelerator, &escape_target_high);
EXPECT_FALSE(focus_manager->HasPriorityHandler(escape_accelerator));
// Hit the escape key (no change, no targets registered).
EXPECT_FALSE(focus_manager->ProcessAccelerator(escape_accelerator));
EXPECT_EQ(escape_target_high.accelerator_count(), 4);
EXPECT_EQ(escape_target_normal.accelerator_count(), 1);
}
TEST_F(FocusManagerTest, CallsEnabledAcceleratorTargetsOnly) {
FocusManager* focus_manager = GetFocusManager();
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
ui::TestAcceleratorTarget return_target1(true);
ui::TestAcceleratorTarget return_target2(true);
focus_manager->RegisterAccelerator(return_accelerator,
ui::AcceleratorManager::kNormalPriority,
&return_target1);
focus_manager->RegisterAccelerator(return_accelerator,
ui::AcceleratorManager::kNormalPriority,
&return_target2);
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(0, return_target1.accelerator_count());
EXPECT_EQ(1, return_target2.accelerator_count());
// If CanHandleAccelerators() return false, FocusManager shouldn't call
// AcceleratorPressed().
return_target2.set_can_handle_accelerators(false);
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(1, return_target1.accelerator_count());
EXPECT_EQ(1, return_target2.accelerator_count());
// If no accelerator targets are enabled, ProcessAccelerator() should fail.
return_target1.set_can_handle_accelerators(false);
EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(1, return_target1.accelerator_count());
EXPECT_EQ(1, return_target2.accelerator_count());
// Enabling the target again causes the accelerators to be processed again.
return_target1.set_can_handle_accelerators(true);
return_target2.set_can_handle_accelerators(true);
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(1, return_target1.accelerator_count());
EXPECT_EQ(2, return_target2.accelerator_count());
}
// Unregisters itself when its accelerator is invoked.
class SelfUnregisteringAcceleratorTarget : public ui::TestAcceleratorTarget {
public:
SelfUnregisteringAcceleratorTarget(const ui::Accelerator& accelerator,
FocusManager* focus_manager)
: accelerator_(accelerator), focus_manager_(focus_manager) {}
SelfUnregisteringAcceleratorTarget(
const SelfUnregisteringAcceleratorTarget&) = delete;
SelfUnregisteringAcceleratorTarget& operator=(
const SelfUnregisteringAcceleratorTarget&) = delete;
// ui::TestAcceleratorTarget:
bool AcceleratorPressed(const ui::Accelerator& accelerator) override {
focus_manager_->UnregisterAccelerator(accelerator, this);
return ui::TestAcceleratorTarget::AcceleratorPressed(accelerator);
}
private:
ui::Accelerator accelerator_;
FocusManager* focus_manager_;
};
TEST_F(FocusManagerTest, CallsSelfDeletingAcceleratorTarget) {
FocusManager* focus_manager = GetFocusManager();
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
SelfUnregisteringAcceleratorTarget target(return_accelerator, focus_manager);
EXPECT_EQ(target.accelerator_count(), 0);
// Register the target.
focus_manager->RegisterAccelerator(
return_accelerator, ui::AcceleratorManager::kNormalPriority, &target);
// Hitting the return key. The target will be unregistered.
EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(target.accelerator_count(), 1);
// Hitting the return key again; nothing should happen.
EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator));
EXPECT_EQ(target.accelerator_count(), 1);
}
TEST_F(FocusManagerTest, SuspendAccelerators) {
const ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
ui::Accelerator accelerator(event.key_code(), event.flags());
ui::TestAcceleratorTarget target(true);
FocusManager* focus_manager = GetFocusManager();
focus_manager->RegisterAccelerator(
accelerator, ui::AcceleratorManager::kNormalPriority, &target);
focus_manager->set_shortcut_handling_suspended(true);
EXPECT_TRUE(focus_manager->OnKeyEvent(event));
EXPECT_EQ(0, target.accelerator_count());
focus_manager->set_shortcut_handling_suspended(false);
EXPECT_FALSE(focus_manager->OnKeyEvent(event));
EXPECT_EQ(1, target.accelerator_count());
}
class FocusManagerDtorTest : public FocusManagerTest {
protected:
using DtorTrackVector = std::vector<std::string>;
class FocusManagerDtorTracked : public FocusManager {
public:
FocusManagerDtorTracked(Widget* widget, DtorTrackVector* dtor_tracker)
: FocusManager(widget, nullptr /* delegate */),
dtor_tracker_(dtor_tracker) {}
FocusManagerDtorTracked(const FocusManagerDtorTracked&) = delete;
FocusManagerDtorTracked& operator=(const FocusManagerDtorTracked&) = delete;
~FocusManagerDtorTracked() override {
dtor_tracker_->push_back("FocusManagerDtorTracked");
}
DtorTrackVector* dtor_tracker_;
};
class TestFocusManagerFactory : public FocusManagerFactory {
public:
explicit TestFocusManagerFactory(DtorTrackVector* dtor_tracker)
: dtor_tracker_(dtor_tracker) {}
TestFocusManagerFactory(const TestFocusManagerFactory&) = delete;
TestFocusManagerFactory& operator=(const TestFocusManagerFactory&) = delete;
~TestFocusManagerFactory() override = default;
std::unique_ptr<FocusManager> CreateFocusManager(Widget* widget) override {
return std::make_unique<FocusManagerDtorTracked>(widget, dtor_tracker_);
}
private:
DtorTrackVector* dtor_tracker_;
};
class WindowDtorTracked : public Widget {
public:
explicit WindowDtorTracked(DtorTrackVector* dtor_tracker)
: dtor_tracker_(dtor_tracker) {}
~WindowDtorTracked() override {
dtor_tracker_->push_back("WindowDtorTracked");
}
DtorTrackVector* dtor_tracker_;
};
void SetUp() override {
ViewsTestBase::SetUp();
FocusManagerFactory::Install(new TestFocusManagerFactory(&dtor_tracker_));
// Create WindowDtorTracked that uses FocusManagerDtorTracked.
Widget* widget = new WindowDtorTracked(&dtor_tracker_);
Widget::InitParams params;
params.delegate = this;
params.bounds = gfx::Rect(0, 0, 100, 100);
widget->Init(std::move(params));
tracked_focus_manager_ =
static_cast<FocusManagerDtorTracked*>(GetFocusManager());
widget->Show();
}
void TearDown() override {
FocusManagerFactory::Install(nullptr);
ViewsTestBase::TearDown();
}
FocusManager* tracked_focus_manager_;
DtorTrackVector dtor_tracker_;
};
namespace {
class FocusInAboutToRequestFocusFromTabTraversalView : public View {
public:
FocusInAboutToRequestFocusFromTabTraversalView() = default;
FocusInAboutToRequestFocusFromTabTraversalView(
const FocusInAboutToRequestFocusFromTabTraversalView&) = delete;
FocusInAboutToRequestFocusFromTabTraversalView& operator=(
const FocusInAboutToRequestFocusFromTabTraversalView&) = delete;
void set_view_to_focus(View* view) { view_to_focus_ = view; }
void AboutToRequestFocusFromTabTraversal(bool reverse) override {
view_to_focus_->RequestFocus();
}
private:
views::View* view_to_focus_ = nullptr;
};
} // namespace
// Verifies a focus change done during a call to
// AboutToRequestFocusFromTabTraversal() is honored.
TEST_F(FocusManagerTest, FocusInAboutToRequestFocusFromTabTraversal) {
// Create 3 views focuses the 3 and advances to the second. The 2nd views
// implementation of AboutToRequestFocusFromTabTraversal() focuses the first.
views::View* v1 = new View;
v1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(v1);
FocusInAboutToRequestFocusFromTabTraversalView* v2 =
new FocusInAboutToRequestFocusFromTabTraversalView;
v2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
v2->set_view_to_focus(v1);
GetContentsView()->AddChildView(v2);
views::View* v3 = new View;
v3->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(v3);
v3->RequestFocus();
GetWidget()->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(v1->HasFocus());
}
TEST_F(FocusManagerTest, RotatePaneFocus) {
views::AccessiblePaneView* pane1 = new AccessiblePaneView();
GetContentsView()->AddChildView(pane1);
views::View* v1 = new View;
v1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
pane1->AddChildView(v1);
views::View* v2 = new View;
v2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
pane1->AddChildView(v2);
views::AccessiblePaneView* pane2 = new AccessiblePaneView();
GetContentsView()->AddChildView(pane2);
views::View* v3 = new View;
v3->SetFocusBehavior(View::FocusBehavior::ALWAYS);
pane2->AddChildView(v3);
views::View* v4 = new View;
v4->SetFocusBehavior(View::FocusBehavior::ALWAYS);
pane2->AddChildView(v4);
std::vector<views::View*> panes;
panes.push_back(pane1);
panes.push_back(pane2);
SetAccessiblePanes(panes);
FocusManager* focus_manager = GetWidget()->GetFocusManager();
// Advance forwards. Focus should stay trapped within each pane.
EXPECT_TRUE(focus_manager->RotatePaneFocus(
FocusManager::kForward, FocusManager::FocusCycleWrapping::kEnabled));
EXPECT_EQ(v1, focus_manager->GetFocusedView());
focus_manager->AdvanceFocus(false);
EXPECT_EQ(v2, focus_manager->GetFocusedView());
focus_manager->AdvanceFocus(false);
EXPECT_EQ(v1, focus_manager->GetFocusedView());
EXPECT_TRUE(focus_manager->RotatePaneFocus(
FocusManager::kForward, FocusManager::FocusCycleWrapping::kEnabled));
EXPECT_EQ(v3, focus_manager->GetFocusedView());
focus_manager->AdvanceFocus(false);
EXPECT_EQ(v4, focus_manager->GetFocusedView());
focus_manager->AdvanceFocus(false);
EXPECT_EQ(v3, focus_manager->GetFocusedView());
EXPECT_TRUE(focus_manager->RotatePaneFocus(
FocusManager::kForward, FocusManager::FocusCycleWrapping::kEnabled));
EXPECT_EQ(v1, focus_manager->GetFocusedView());
// Advance backwards.
EXPECT_TRUE(focus_manager->RotatePaneFocus(
FocusManager::kBackward, FocusManager::FocusCycleWrapping::kEnabled));
EXPECT_EQ(v3, focus_manager->GetFocusedView());
EXPECT_TRUE(focus_manager->RotatePaneFocus(
FocusManager::kBackward, FocusManager::FocusCycleWrapping::kEnabled));
EXPECT_EQ(v1, focus_manager->GetFocusedView());
// Advance without wrap. When it gets to the end of the list of
// panes, RotatePaneFocus should return false but the current
// focused view shouldn't change.
EXPECT_TRUE(focus_manager->RotatePaneFocus(
FocusManager::kForward, FocusManager::FocusCycleWrapping::kDisabled));
EXPECT_EQ(v3, focus_manager->GetFocusedView());
EXPECT_FALSE(focus_manager->RotatePaneFocus(
FocusManager::kForward, FocusManager::FocusCycleWrapping::kDisabled));
EXPECT_EQ(v3, focus_manager->GetFocusedView());
}
// Verifies the stored focus view tracks the focused view.
TEST_F(FocusManagerTest, ImplicitlyStoresFocus) {
views::View* v1 = new View;
v1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(v1);
views::View* v2 = new View;
v2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(v2);
// Verify a focus request on |v1| implicitly updates the stored focus view.
v1->RequestFocus();
EXPECT_TRUE(v1->HasFocus());
EXPECT_EQ(v1, GetWidget()->GetFocusManager()->GetStoredFocusView());
// Verify a focus request on |v2| implicitly updates the stored focus view.
v2->RequestFocus();
EXPECT_TRUE(v2->HasFocus());
EXPECT_EQ(v2, GetWidget()->GetFocusManager()->GetStoredFocusView());
}
namespace {
class FocusManagerArrowKeyTraversalTest
: public FocusManagerTest,
public testing::WithParamInterface<bool> {
public:
FocusManagerArrowKeyTraversalTest() = default;
FocusManagerArrowKeyTraversalTest(const FocusManagerArrowKeyTraversalTest&) =
delete;
FocusManagerArrowKeyTraversalTest& operator=(
const FocusManagerArrowKeyTraversalTest&) = delete;
~FocusManagerArrowKeyTraversalTest() override = default;
// FocusManagerTest overrides:
void SetUp() override {
if (testing::UnitTest::GetInstance()->current_test_info()->value_param()) {
is_rtl_ = GetParam();
if (is_rtl_)
base::i18n::SetICUDefaultLocale("he");
}
FocusManagerTest::SetUp();
previous_arrow_key_traversal_enabled_ =
FocusManager::arrow_key_traversal_enabled();
}
void TearDown() override {
FocusManager::set_arrow_key_traversal_enabled(
previous_arrow_key_traversal_enabled_);
FocusManagerTest::TearDown();
}
bool is_rtl_ = false;
private:
// Restores the locale to default when the destructor is called.
base::test::ScopedRestoreICUDefaultLocale restore_locale_;
bool previous_arrow_key_traversal_enabled_ = false;
};
// Instantiate the Boolean which is used to toggle RTL in
// the parameterized tests.
INSTANTIATE_TEST_SUITE_P(All,
FocusManagerArrowKeyTraversalTest,
testing::Bool());
} // namespace
TEST_P(FocusManagerArrowKeyTraversalTest, ArrowKeyTraversal) {
FocusManager* focus_manager = GetFocusManager();
const ui::KeyEvent left_key(ui::ET_KEY_PRESSED, ui::VKEY_LEFT, ui::EF_NONE);
const ui::KeyEvent right_key(ui::ET_KEY_PRESSED, ui::VKEY_RIGHT, ui::EF_NONE);
const ui::KeyEvent up_key(ui::ET_KEY_PRESSED, ui::VKEY_UP, ui::EF_NONE);
const ui::KeyEvent down_key(ui::ET_KEY_PRESSED, ui::VKEY_DOWN, ui::EF_NONE);
std::vector<views::View*> v;
for (size_t i = 0; i < 2; ++i) {
views::View* view = new View;
view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(view);
v.push_back(view);
}
// Arrow key traversal is off and arrow key does not change focus.
FocusManager::set_arrow_key_traversal_enabled(false);
v[0]->RequestFocus();
focus_manager->OnKeyEvent(right_key);
EXPECT_EQ(v[0], focus_manager->GetFocusedView());
focus_manager->OnKeyEvent(left_key);
EXPECT_EQ(v[0], focus_manager->GetFocusedView());
focus_manager->OnKeyEvent(down_key);
EXPECT_EQ(v[0], focus_manager->GetFocusedView());
focus_manager->OnKeyEvent(up_key);
EXPECT_EQ(v[0], focus_manager->GetFocusedView());
// Turn on arrow key traversal.
FocusManager::set_arrow_key_traversal_enabled(true);
v[0]->RequestFocus();
focus_manager->OnKeyEvent(is_rtl_ ? left_key : right_key);
EXPECT_EQ(v[1], focus_manager->GetFocusedView());
focus_manager->OnKeyEvent(is_rtl_ ? right_key : left_key);
EXPECT_EQ(v[0], focus_manager->GetFocusedView());
focus_manager->OnKeyEvent(down_key);
EXPECT_EQ(v[1], focus_manager->GetFocusedView());
focus_manager->OnKeyEvent(up_key);
EXPECT_EQ(v[0], focus_manager->GetFocusedView());
}
TEST_F(FocusManagerTest, StoreFocusedView) {
std::vector<FocusTestEvent> event_list;
const int kView1ID = 1;
SimpleTestView* view = new SimpleTestView(&event_list, kView1ID);
// Add view to the view hierarchy and make it focusable.
GetWidget()->GetRootView()->AddChildView(view);
view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetFocusManager()->SetFocusedView(view);
GetFocusManager()->StoreFocusedView(false);
EXPECT_EQ(nullptr, GetFocusManager()->GetFocusedView());
EXPECT_TRUE(GetFocusManager()->RestoreFocusedView());
EXPECT_EQ(view, GetFocusManager()->GetStoredFocusView());
ASSERT_EQ(3, static_cast<int>(event_list.size()));
EXPECT_EQ(ON_FOCUS, event_list[0].type);
EXPECT_EQ(kView1ID, event_list[0].view_id);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[0].focus_change_reason);
EXPECT_EQ(ON_BLUR, event_list[1].type);
EXPECT_EQ(kView1ID, event_list[1].view_id);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[1].focus_change_reason);
EXPECT_EQ(ON_FOCUS, event_list[2].type);
EXPECT_EQ(kView1ID, event_list[2].view_id);
EXPECT_EQ(FocusChangeReason::kFocusRestore,
event_list[2].focus_change_reason);
// Repeat with |true|.
event_list.clear();
GetFocusManager()->SetFocusedView(view);
GetFocusManager()->StoreFocusedView(true);
EXPECT_EQ(nullptr, GetFocusManager()->GetFocusedView());
EXPECT_TRUE(GetFocusManager()->RestoreFocusedView());
EXPECT_EQ(view, GetFocusManager()->GetStoredFocusView());
ASSERT_EQ(2, static_cast<int>(event_list.size()));
EXPECT_EQ(ON_BLUR, event_list[0].type);
EXPECT_EQ(kView1ID, event_list[0].view_id);
EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
event_list[0].focus_change_reason);
EXPECT_EQ(ON_FOCUS, event_list[1].type);
EXPECT_EQ(kView1ID, event_list[1].view_id);
EXPECT_EQ(FocusChangeReason::kFocusRestore,
event_list[1].focus_change_reason);
// Necessary for clean teardown.
GetFocusManager()->ClearFocus();
}
#if defined(OS_MACOSX)
// Test that the correct view is restored if full keyboard access is changed.
TEST_F(FocusManagerTest, StoreFocusedViewFullKeyboardAccess) {
View* view1 = new View;
View* view2 = new View;
View* view3 = new View;
// Make view1 focusable in accessibility mode, view2 not focusable and view3
// always focusable.
view1->SetFocusBehavior(View::FocusBehavior::ACCESSIBLE_ONLY);
view2->SetFocusBehavior(View::FocusBehavior::NEVER);
view3->SetFocusBehavior(View::FocusBehavior::ALWAYS);
// Add views to the view hierarchy
GetWidget()->GetRootView()->AddChildView(view1);
GetWidget()->GetRootView()->AddChildView(view2);
GetWidget()->GetRootView()->AddChildView(view3);
view1->RequestFocus();
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
GetFocusManager()->StoreFocusedView(true);
EXPECT_EQ(nullptr, GetFocusManager()->GetFocusedView());
// Turn off full keyboard access mode and restore focused view. Since view1 is
// no longer focusable, view3 should have focus.
GetFocusManager()->SetKeyboardAccessible(false);
EXPECT_FALSE(GetFocusManager()->RestoreFocusedView());
EXPECT_EQ(view3, GetFocusManager()->GetFocusedView());
GetFocusManager()->StoreFocusedView(false);
EXPECT_EQ(nullptr, GetFocusManager()->GetFocusedView());
// Turn on full keyboard access mode and restore focused view. Since view3 is
// still focusable, view3 should have focus.
GetFocusManager()->SetKeyboardAccessible(true);
EXPECT_TRUE(GetFocusManager()->RestoreFocusedView());
EXPECT_EQ(view3, GetFocusManager()->GetFocusedView());
}
// Test that View::RequestFocus() respects full keyboard access mode.
TEST_F(FocusManagerTest, RequestFocus) {
View* view1 = new View();
View* view2 = new View();
// Make view1 always focusable, view2 only focusable in accessibility mode.
view1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
view2->SetFocusBehavior(View::FocusBehavior::ACCESSIBLE_ONLY);
// Adds views to the view hierarchy.
GetWidget()->GetRootView()->AddChildView(view1);
GetWidget()->GetRootView()->AddChildView(view2);
// Verify view1 can always get focus via View::RequestFocus, while view2 can
// only get focus in full keyboard accessibility mode.
EXPECT_TRUE(GetFocusManager()->keyboard_accessible());
view1->RequestFocus();
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
view2->RequestFocus();
EXPECT_EQ(view2, GetFocusManager()->GetFocusedView());
// Toggle full keyboard accessibility.
GetFocusManager()->SetKeyboardAccessible(false);
GetFocusManager()->ClearFocus();
EXPECT_NE(view1, GetFocusManager()->GetFocusedView());
view1->RequestFocus();
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
view2->RequestFocus();
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
}
#endif
namespace {
// Trivial WidgetDelegate implementation that allows setting return value of
// ShouldAdvanceFocusToTopLevelWidget().
class AdvanceFocusWidgetDelegate : public WidgetDelegate {
public:
explicit AdvanceFocusWidgetDelegate(Widget* widget) : widget_(widget) {}
AdvanceFocusWidgetDelegate(const AdvanceFocusWidgetDelegate&) = delete;
AdvanceFocusWidgetDelegate& operator=(const AdvanceFocusWidgetDelegate&) =
delete;
~AdvanceFocusWidgetDelegate() override = default;
// WidgetDelegate:
Widget* GetWidget() override { return widget_; }
const Widget* GetWidget() const override { return widget_; }
private:
Widget* widget_;
};
class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
public:
explicit TestBubbleDialogDelegateView(View* anchor)
: BubbleDialogDelegateView(anchor, BubbleBorder::NONE) {
DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
}
TestBubbleDialogDelegateView(const TestBubbleDialogDelegateView&) = delete;
TestBubbleDialogDelegateView& operator=(const TestBubbleDialogDelegateView&) =
delete;
~TestBubbleDialogDelegateView() override = default;
static TestBubbleDialogDelegateView* CreateAndShowBubble(View* anchor) {
TestBubbleDialogDelegateView* bubble =
new TestBubbleDialogDelegateView(anchor);
Widget* bubble_widget = BubbleDialogDelegateView::CreateBubble(bubble);
bubble_widget->SetFocusTraversableParent(
bubble->anchor_widget()->GetFocusTraversable());
bubble_widget->SetFocusTraversableParentView(anchor);
bubble->set_close_on_deactivate(false);
bubble_widget->Show();
return bubble;
}
// If this is called, the bubble will be forced to use a NativeWidgetAura.
// If not set, it might get a DesktopNativeWidgetAura depending on the
// platform and other factors.
void UseNativeWidgetAura() { use_native_widget_aura_ = true; }
void OnBeforeBubbleWidgetInit(Widget::InitParams* params,
Widget* widget) const override {
#if defined(USE_AURA)
if (use_native_widget_aura_) {
params->native_widget =
new test::TestPlatformNativeWidget<NativeWidgetAura>(widget, false,
nullptr);
}
#endif // USE_AURA
}
private:
bool use_native_widget_aura_ = false;
};
} // namespace
// Verifies focus wrapping happens in the same widget.
TEST_F(FocusManagerTest, AdvanceFocusStaysInWidget) {
// Add |widget_view| as a child of the Widget.
View* widget_view = new View;
widget_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
widget_view->SetBounds(20, 0, 20, 20);
GetContentsView()->AddChildView(widget_view);
// Create a widget with two views, focus the second.
std::unique_ptr<AdvanceFocusWidgetDelegate> delegate;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.child = true;
params.bounds = gfx::Rect(10, 10, 100, 100);
params.parent = GetWidget()->GetNativeView();
Widget child_widget;
delegate = std::make_unique<AdvanceFocusWidgetDelegate>(&child_widget);
params.delegate = delegate.get();
child_widget.Init(std::move(params));
View* view1 = new View;
view1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
view1->SetBounds(0, 0, 20, 20);
View* view2 = new View;
view2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
view2->SetBounds(20, 0, 20, 20);
child_widget.client_view()->AddChildView(view1);
child_widget.client_view()->AddChildView(view2);
child_widget.Show();
view2->RequestFocus();
EXPECT_EQ(view2, GetFocusManager()->GetFocusedView());
// Advance focus backwards, which should focus the first.
GetFocusManager()->AdvanceFocus(false);
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
// Focus forward to |view2|.
GetFocusManager()->AdvanceFocus(true);
EXPECT_EQ(view2, GetFocusManager()->GetFocusedView());
// And forward again, wrapping back to |view1|.
GetFocusManager()->AdvanceFocus(true);
EXPECT_EQ(view1, GetFocusManager()->GetFocusedView());
// Allow focus to go to the parent, and focus backwards which should now move
// up |widget_view| (in the parent).
delegate->SetFocusTraversesOut(true);
GetFocusManager()->AdvanceFocus(true);
EXPECT_EQ(widget_view, GetFocusManager()->GetFocusedView());
}
TEST_F(FocusManagerTest, NavigateIntoAnchoredDialog) {
// The parent Widget has four focusable views. A child widget dialog has
// two focusable views, and it's anchored to the 3rd parent view. Ensure
// that focus traverses into the anchored dialog after the 3rd parent
// view, and then back to the 4th parent view.
View* parent1 = new View();
View* parent2 = new View();
View* parent3 = new View();
View* parent4 = new View();
parent1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent3->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent4->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetWidget()->GetRootView()->AddChildView(parent1);
GetWidget()->GetRootView()->AddChildView(parent2);
GetWidget()->GetRootView()->AddChildView(parent3);
GetWidget()->GetRootView()->AddChildView(parent4);
// Add an unfocusable child view to the dialog anchor view. This is a
// regression test that makes sure focus is able to navigate past unfocusable
// children and try to go into the anchored dialog. |kAnchoredDialogKey| was
// previously not checked if a recursive search to find a focusable child view
// was attempted (and failed), so the dialog would previously be skipped.
parent3->AddChildView(new View());
BubbleDialogDelegateView* bubble_delegate =
TestBubbleDialogDelegateView::CreateAndShowBubble(parent3);
Widget* bubble_widget = bubble_delegate->GetWidget();
View* child1 = new View();
View* child2 = new View();
child1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
child2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
bubble_widget->GetRootView()->AddChildView(child1);
bubble_widget->GetRootView()->AddChildView(child2);
parent1->RequestFocus();
// Navigate forwards
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(parent2->HasFocus());
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(parent3->HasFocus());
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(child1->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(child2->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(parent4->HasFocus());
// Navigate backwards
GetWidget()->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(child2->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(child1->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(parent3->HasFocus());
}
TEST_F(FocusManagerTest, AnchoredDialogOnContainerView) {
// The parent Widget has four focusable views, with the middle two views
// inside of a non-focusable grouping View. A child widget dialog has
// two focusable views, and it's anchored to the group View. Ensure
// that focus traverses into the anchored dialog after the 3rd parent
// view, and then back to the 4th parent view.
View* parent1 = new View();
View* parent2 = new View();
View* parent3 = new View();
View* parent4 = new View();
View* parent_group = new View();
parent1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent3->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent4->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetWidget()->GetRootView()->AddChildView(parent1);
GetWidget()->GetRootView()->AddChildView(parent_group);
parent_group->AddChildView(parent2);
parent_group->AddChildView(parent3);
GetWidget()->GetRootView()->AddChildView(parent4);
BubbleDialogDelegateView* bubble_delegate =
TestBubbleDialogDelegateView::CreateAndShowBubble(parent3);
Widget* bubble_widget = bubble_delegate->GetWidget();
View* child1 = new View();
View* child2 = new View();
child1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
child2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
bubble_widget->GetRootView()->AddChildView(child1);
bubble_widget->GetRootView()->AddChildView(child2);
parent1->RequestFocus();
// Navigate forwards
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(parent2->HasFocus());
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(parent3->HasFocus());
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(child1->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(child2->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(parent4->HasFocus());
// Navigate backwards
GetWidget()->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(child2->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(child1->HasFocus());
bubble_widget->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(parent3->HasFocus());
}
// Checks that focus traverses from a View to a bubble anchored at that View
// when in a pane.
TEST_F(FocusManagerTest, AnchoredDialogInPane) {
// Set up a focusable view (to which we will anchor our bubble) inside an
// AccessiblePaneView.
View* root_view = GetWidget()->GetRootView();
AccessiblePaneView* pane =
root_view->AddChildView(std::make_unique<AccessiblePaneView>());
View* anchor = pane->AddChildView(std::make_unique<View>());
anchor->SetFocusBehavior(View::FocusBehavior::ALWAYS);
BubbleDialogDelegateView* bubble =
TestBubbleDialogDelegateView::CreateAndShowBubble(anchor);
// We need a focusable view inside our bubble to check that focus traverses
// in.
View* bubble_child = bubble->AddChildView(std::make_unique<View>());
bubble_child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
// Verify that, when in pane focus mode, focus advances from the anchor view
// to inside the bubble.
pane->SetPaneFocus(anchor);
EXPECT_TRUE(anchor->HasFocus());
GetWidget()->GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(bubble_child->HasFocus());
}
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
// This test is specifically for the permutation where the main widget is a
// DesktopNativeWidgetAura and the bubble is a NativeWidgetAura. When focus
// moves back from the bubble to the parent widget, ensure that the DNWA's aura
// window is focused.
class DesktopWidgetFocusManagerTest : public FocusManagerTest {
public:
DesktopWidgetFocusManagerTest() = default;
DesktopWidgetFocusManagerTest(const DesktopWidgetFocusManagerTest&) = delete;
DesktopWidgetFocusManagerTest& operator=(
const DesktopWidgetFocusManagerTest&) = delete;
~DesktopWidgetFocusManagerTest() override = default;
// FocusManagerTest:
void SetUp() override {
set_native_widget_type(NativeWidgetType::kDesktop);
FocusManagerTest::SetUp();
}
};
TEST_F(DesktopWidgetFocusManagerTest, AnchoredDialogInDesktopNativeWidgetAura) {
Widget widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 1024, 768);
widget.Init(std::move(params));
widget.Show();
widget.Activate();
View* parent1 = new View();
View* parent2 = new View();
parent1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
parent2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
widget.GetRootView()->AddChildView(parent1);
widget.GetRootView()->AddChildView(parent2);
TestBubbleDialogDelegateView* bubble_delegate =
TestBubbleDialogDelegateView::CreateAndShowBubble(parent2);
Widget* bubble_widget = bubble_delegate->GetWidget();
bubble_delegate->UseNativeWidgetAura();
View* child = new View();
child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
bubble_widget->GetRootView()->AddChildView(child);
widget.Activate();
parent1->RequestFocus();
base::RunLoop().RunUntilIdle();
// Initially the outer widget's window is focused.
aura::client::FocusClient* focus_client =
aura::client::GetFocusClient(widget.GetNativeView());
ASSERT_EQ(widget.GetNativeView(), focus_client->GetFocusedWindow());
// Navigate forwards
widget.GetFocusManager()->AdvanceFocus(false);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(parent2->HasFocus());
widget.GetFocusManager()->AdvanceFocus(false);
EXPECT_TRUE(child->HasFocus());
// Now the bubble widget's window is focused.
ASSERT_NE(widget.GetNativeView(), focus_client->GetFocusedWindow());
ASSERT_EQ(bubble_widget->GetNativeView(), focus_client->GetFocusedWindow());
// Navigate backwards
bubble_widget->GetFocusManager()->AdvanceFocus(true);
EXPECT_TRUE(parent2->HasFocus());
// Finally, the outer widget's window should be focused again.
ASSERT_EQ(widget.GetNativeView(), focus_client->GetFocusedWindow());
}
#endif
// Ensures graceful failure if there is a focus cycle.
TEST_F(FocusManagerTest, HandlesFocusCycles) {
// Create two side-by-side views.
View* root_view = GetWidget()->GetRootView();
View* left = root_view->AddChildView(std::make_unique<View>());
View* right = root_view->AddChildView(std::make_unique<View>());
// Create a cycle where the left view is focusable and the right isn't.
left->SetFocusBehavior(View::FocusBehavior::ALWAYS);
right->SetFocusBehavior(View::FocusBehavior::NEVER);
left->SetNextFocusableView(right);
right->SetNextFocusableView(left);
// Set focus on the left view then make it unfocusable, which both advances
// focus and ensures there's no candidate for focusing.
left->RequestFocus();
EXPECT_TRUE(left->HasFocus());
left->SetFocusBehavior(View::FocusBehavior::NEVER);
// At this point, we didn't crash. Just as a sanity check, ensure neither of
// our views were incorrectly focused.
EXPECT_FALSE(left->HasFocus());
EXPECT_FALSE(right->HasFocus());
// Now test focusing in reverse.
GetFocusManager()->SetFocusedView(right);
EXPECT_TRUE(right->HasFocus());
GetFocusManager()->AdvanceFocus(true);
// We don't check whether |right| has focus since if no focusable view is
// found, AdvanceFocus() doesn't clear focus.
EXPECT_FALSE(left->HasFocus());
}
#if defined(USE_AURA)
class RedirectToParentFocusManagerTest : public FocusManagerTest {
public:
RedirectToParentFocusManagerTest() = default;
RedirectToParentFocusManagerTest(const RedirectToParentFocusManagerTest&) =
delete;
RedirectToParentFocusManagerTest& operator=(
const RedirectToParentFocusManagerTest&) = delete;
~RedirectToParentFocusManagerTest() override = default;
// FocusManagerTest:
void SetUp() override {
FocusManagerTest::SetUp();
View* anchor =
GetWidget()->GetRootView()->AddChildView(std::make_unique<View>());
anchor->SetFocusBehavior(View::FocusBehavior::ALWAYS);
BubbleDialogDelegateView* bubble_delegate =
TestBubbleDialogDelegateView::CreateAndShowBubble(anchor);
Widget* bubble_widget = bubble_delegate->GetWidget();
parent_focus_manager_ = anchor->GetFocusManager();
bubble_focus_manager_ = bubble_widget->GetFocusManager();
}
void TearDown() override {
FocusManagerFactory::Install(nullptr);
FocusManagerTest::TearDown();
}
protected:
FocusManager* parent_focus_manager_;
FocusManager* bubble_focus_manager_;
};
// Test that when an accelerator is sent to a bubble that isn't registered,
// the bubble's parent handles it instead.
TEST_F(RedirectToParentFocusManagerTest, ParentHandlesAcceleratorFromBubble) {
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
ui::TestAcceleratorTarget parent_return_target(true);
EXPECT_EQ(0, parent_return_target.accelerator_count());
parent_focus_manager_->RegisterAccelerator(
return_accelerator, ui::AcceleratorManager::kNormalPriority,
&parent_return_target);
EXPECT_TRUE(
!bubble_focus_manager_->IsAcceleratorRegistered(return_accelerator));
// Accelerator was proccesed by the parent.
EXPECT_TRUE(bubble_focus_manager_->ProcessAccelerator(return_accelerator));
EXPECT_EQ(parent_return_target.accelerator_count(), 1);
}
// Test that when an accelerator is sent to a bubble that is registered on both
// it and its parent, the bubble handles it.
TEST_F(RedirectToParentFocusManagerTest, BubbleHandlesRegisteredAccelerators) {
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
ui::TestAcceleratorTarget parent_return_target(true);
ui::TestAcceleratorTarget bubble_return_target(true);
EXPECT_EQ(0, bubble_return_target.accelerator_count());
EXPECT_EQ(0, parent_return_target.accelerator_count());
bubble_focus_manager_->RegisterAccelerator(
return_accelerator, ui::AcceleratorManager::kNormalPriority,
&bubble_return_target);
parent_focus_manager_->RegisterAccelerator(
return_accelerator, ui::AcceleratorManager::kNormalPriority,
&parent_return_target);
// Accelerator was proccesed by the bubble and not by the parent.
EXPECT_TRUE(bubble_focus_manager_->ProcessAccelerator(return_accelerator));
EXPECT_EQ(1, bubble_return_target.accelerator_count());
EXPECT_EQ(0, parent_return_target.accelerator_count());
}
#endif
} // namespace views