// 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/accessible_pane_view.h" #include #include "base/memory/raw_ptr.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "ui/base/accelerators/accelerator.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/test/views_test_base.h" #include "ui/views/widget/widget.h" namespace views { // TODO(alicet): bring pane rotation into views and add tests. // See browser_view.cc for details. using AccessiblePaneViewTest = ViewsTestBase; class TestBarView : public AccessiblePaneView { public: TestBarView(); TestBarView(const TestBarView&) = delete; TestBarView& operator=(const TestBarView&) = delete; ~TestBarView() override; LabelButton* child_button() const { return child_button_; } LabelButton* second_child_button() const { return second_child_button_; } LabelButton* third_child_button() const { return third_child_button_; } LabelButton* not_child_button() const { return not_child_button_.get(); } View* GetDefaultFocusableChild() override; private: void Init(); raw_ptr child_button_; // TODO(crbug.com/1298696): Breaks views_unittests. raw_ptr second_child_button_; raw_ptr third_child_button_; std::unique_ptr not_child_button_; }; TestBarView::TestBarView() { Init(); set_allow_deactivate_on_esc(true); } TestBarView::~TestBarView() = default; void TestBarView::Init() { SetLayoutManager(std::make_unique()); std::u16string label; child_button_ = AddChildView(std::make_unique()); second_child_button_ = AddChildView(std::make_unique()); third_child_button_ = AddChildView(std::make_unique()); not_child_button_ = std::make_unique(); } View* TestBarView::GetDefaultFocusableChild() { return child_button_; } TEST_F(AccessiblePaneViewTest, SimpleSetPaneFocus) { TestBarView* test_view = new TestBarView(); std::unique_ptr widget(new Widget()); Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params.bounds = gfx::Rect(50, 50, 650, 650); widget->Init(std::move(params)); View* root = widget->GetRootView(); root->AddChildView(test_view); widget->Show(); widget->Activate(); // Set pane focus succeeds, focus on child. EXPECT_TRUE(test_view->SetPaneFocusAndFocusDefault()); EXPECT_EQ(test_view, test_view->GetPaneFocusTraversable()); EXPECT_EQ(test_view->child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); // Set focus on non child view, focus failed, stays on pane. EXPECT_TRUE(test_view->SetPaneFocus(test_view->not_child_button())); EXPECT_FALSE(test_view->not_child_button() == test_view->GetWidget()->GetFocusManager()->GetFocusedView()); EXPECT_EQ(test_view->child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); widget->CloseNow(); widget.reset(); } TEST_F(AccessiblePaneViewTest, SetPaneFocusAndRestore) { View* test_view_main = new View(); std::unique_ptr widget_main(new Widget()); Widget::InitParams params_main = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); params_main.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params_main.bounds = gfx::Rect(0, 0, 20, 20); widget_main->Init(std::move(params_main)); View* root_main = widget_main->GetRootView(); root_main->AddChildView(test_view_main); widget_main->Show(); widget_main->Activate(); test_view_main->GetFocusManager()->SetFocusedView(test_view_main); EXPECT_TRUE(widget_main->IsActive()); EXPECT_TRUE(test_view_main->HasFocus()); TestBarView* test_view_bar = new TestBarView(); std::unique_ptr widget_bar(new Widget()); Widget::InitParams params_bar = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); params_bar.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params_bar.bounds = gfx::Rect(50, 50, 650, 650); widget_bar->Init(std::move(params_bar)); View* root_bar = widget_bar->GetRootView(); root_bar->AddChildView(test_view_bar); widget_bar->Show(); widget_bar->Activate(); // Set pane focus succeeds, focus on child. EXPECT_TRUE(test_view_bar->SetPaneFocusAndFocusDefault()); EXPECT_FALSE(test_view_main->HasFocus()); EXPECT_FALSE(widget_main->IsActive()); EXPECT_EQ(test_view_bar, test_view_bar->GetPaneFocusTraversable()); EXPECT_EQ(test_view_bar->child_button(), test_view_bar->GetWidget()->GetFocusManager()->GetFocusedView()); // Deactivate() is only reliable on Ash. On Windows it uses // ::GetNextWindow() to simply activate another window, and which one is not // predictable. On Mac, Deactivate() is not implemented. Note that // TestBarView calls set_allow_deactivate_on_esc(true), which is only // otherwise used in Ash. #if !BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH) // Esc should deactivate the widget. test_view_bar->AcceleratorPressed(test_view_bar->escape_key()); EXPECT_TRUE(widget_main->IsActive()); EXPECT_FALSE(widget_bar->IsActive()); #endif widget_bar->CloseNow(); widget_bar.reset(); widget_main->CloseNow(); widget_main.reset(); } TEST_F(AccessiblePaneViewTest, TwoSetPaneFocus) { TestBarView* test_view = new TestBarView(); TestBarView* test_view_2 = new TestBarView(); std::unique_ptr widget(new Widget()); Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params.bounds = gfx::Rect(50, 50, 650, 650); widget->Init(std::move(params)); View* root = widget->GetRootView(); root->AddChildView(test_view); root->AddChildView(test_view_2); widget->Show(); widget->Activate(); // Set pane focus succeeds, focus on child. EXPECT_TRUE(test_view->SetPaneFocusAndFocusDefault()); EXPECT_EQ(test_view, test_view->GetPaneFocusTraversable()); EXPECT_EQ(test_view->child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); // Set focus on another test_view, focus move to that pane. EXPECT_TRUE(test_view_2->SetPaneFocus(test_view_2->second_child_button())); EXPECT_FALSE(test_view->child_button() == test_view->GetWidget()->GetFocusManager()->GetFocusedView()); EXPECT_EQ(test_view_2->second_child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); widget->CloseNow(); widget.reset(); } TEST_F(AccessiblePaneViewTest, PaneFocusTraversal) { TestBarView* test_view = new TestBarView(); TestBarView* original_test_view = new TestBarView(); std::unique_ptr widget(new Widget()); Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params.bounds = gfx::Rect(50, 50, 650, 650); widget->Init(std::move(params)); View* root = widget->GetRootView(); root->AddChildView(original_test_view); root->AddChildView(test_view); widget->Show(); widget->Activate(); // Set pane focus on first view. EXPECT_TRUE(original_test_view->SetPaneFocus( original_test_view->third_child_button())); // Test traversal in second view. // Set pane focus on second child. EXPECT_TRUE(test_view->SetPaneFocus(test_view->second_child_button())); // home test_view->AcceleratorPressed(test_view->home_key()); EXPECT_EQ(test_view->child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); // end test_view->AcceleratorPressed(test_view->end_key()); EXPECT_EQ(test_view->third_child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); // left test_view->AcceleratorPressed(test_view->left_key()); EXPECT_EQ(test_view->second_child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); // right, right test_view->AcceleratorPressed(test_view->right_key()); test_view->AcceleratorPressed(test_view->right_key()); EXPECT_EQ(test_view->child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); // ESC test_view->AcceleratorPressed(test_view->escape_key()); EXPECT_EQ(original_test_view->third_child_button(), test_view->GetWidget()->GetFocusManager()->GetFocusedView()); widget->CloseNow(); widget.reset(); } // TODO(crbug.com/1314275): Re-enable this test #if defined(ADDRESS_SANITIZER) && defined(LEAK_SANITIZER) #define MAYBE_DoesntCrashOnEscapeWithRemovedView \ DISABLED_DoesntCrashOnEscapeWithRemovedView #else #define MAYBE_DoesntCrashOnEscapeWithRemovedView \ DoesntCrashOnEscapeWithRemovedView #endif TEST_F(AccessiblePaneViewTest, MAYBE_DoesntCrashOnEscapeWithRemovedView) { TestBarView* test_view1 = new TestBarView(); TestBarView* test_view2 = new TestBarView(); Widget widget; Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params.bounds = gfx::Rect(50, 50, 650, 650); widget.Init(std::move(params)); View* root = widget.GetRootView(); root->AddChildView(test_view1); root->AddChildView(test_view2); widget.Show(); widget.Activate(); View* v1 = test_view1->child_button(); View* v2 = test_view2->child_button(); // Do the following: // 1. focus |v1|. // 2. focus |v2|. This makes |test_view2| remember |v1| as having focus. // 3. Removes |v1| from it's parent. // 4. Presses escape on |test_view2|. Escape attempts to revert focus to |v1| // (because of step 2). Because |v1| is not in a widget this should not // attempt to focus anything. EXPECT_TRUE(test_view1->SetPaneFocus(v1)); EXPECT_TRUE(test_view2->SetPaneFocus(v2)); v1->parent()->RemoveChildView(v1); // This shouldn't hit a CHECK in the FocusManager. EXPECT_TRUE(test_view2->AcceleratorPressed(test_view2->escape_key())); } } // namespace views