blob: 8a2c2d9254b2260c4790b2e8c0e0e28caf475fac [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/multi_contents_resize_area.h"
#include "chrome/browser/ui/views/frame/multi_contents_view.h"
#include "chrome/browser/ui/views/frame/multi_contents_view_mini_toolbar.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
#include "url/gurl.h"
const char kSimplePage[] = "/focus/page_with_focus.html";
class BrowserViewFocusTest : public InProcessBrowserTest {
public:
BrowserViewFocusTest() = default;
BrowserViewFocusTest(const BrowserViewFocusTest&) = delete;
BrowserViewFocusTest& operator=(const BrowserViewFocusTest&) = delete;
~BrowserViewFocusTest() override = default;
bool IsViewFocused(ViewID vid) {
return ui_test_utils::IsViewFocused(browser(), vid);
}
};
IN_PROC_BROWSER_TEST_F(BrowserViewFocusTest, BrowsersRememberFocus) {
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
ASSERT_TRUE(embedded_test_server()->Start());
// First we navigate to our test page.
GURL url = embedded_test_server()->GetURL(kSimplePage);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
gfx::NativeWindow window = browser()->window()->GetNativeWindow();
// The focus should be on the Tab contents.
ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
// Now hide the window, show it again, the focus should not have changed.
ui_test_utils::HideNativeWindow(window);
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(window));
ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
chrome::FocusLocationBar(browser());
ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
// Hide the window, show it again, the focus should not have changed.
ui_test_utils::HideNativeWindow(window);
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(window));
ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
// The rest of this test does not make sense on Linux because the behavior
// of Activate() is not well defined and can vary by window manager.
#if BUILDFLAG(IS_WIN)
// Open a new browser window.
Browser* browser2 =
Browser::Create(Browser::CreateParams(browser()->profile(), true));
ASSERT_TRUE(browser2);
chrome::AddTabAt(browser2, GURL(), -1, true);
browser2->window()->Show();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser2, url));
gfx::NativeWindow window2 = browser2->window()->GetNativeWindow();
BrowserView* browser_view2 = BrowserView::GetBrowserViewForBrowser(browser2);
ASSERT_TRUE(browser_view2);
const views::Widget* widget2 =
views::Widget::GetWidgetForNativeWindow(window2);
ASSERT_TRUE(widget2);
const views::FocusManager* focus_manager2 = widget2->GetFocusManager();
ASSERT_TRUE(focus_manager2);
EXPECT_EQ(browser_view2->contents_web_view(),
focus_manager2->GetFocusedView());
// Switch to the 1st browser window, focus should still be on the location
// bar and the second browser should have nothing focused.
browser()->window()->Activate();
ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
EXPECT_EQ(nullptr, focus_manager2->GetFocusedView());
// Switch back to the second browser, focus should still be on the page.
browser2->window()->Activate();
views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
ASSERT_TRUE(widget);
EXPECT_EQ(nullptr, widget->GetFocusManager()->GetFocusedView());
EXPECT_EQ(browser_view2->contents_web_view(),
focus_manager2->GetFocusedView());
// Close the 2nd browser to avoid a DCHECK().
browser_view2->Close();
#endif
}
// Helper class that tracks view classes receiving focus.
class FocusedViewClassRecorder : public views::FocusChangeListener {
public:
explicit FocusedViewClassRecorder(views::FocusManager* focus_manager)
: focus_manager_(focus_manager) {
focus_manager_->AddFocusChangeListener(this);
}
FocusedViewClassRecorder(const FocusedViewClassRecorder&) = delete;
FocusedViewClassRecorder& operator=(const FocusedViewClassRecorder&) = delete;
~FocusedViewClassRecorder() override {
focus_manager_->RemoveFocusChangeListener(this);
}
bool GetHasFocusedOnNonWebView() { return has_focused_on_non_webview_; }
int GetFocusChangeCount() { return focus_change_count; }
private:
// Inherited from views::FocusChangeListener
void OnDidChangeFocus(views::View* focused_before,
views::View* focused_now) override {
if (focused_now) {
if (!views::IsViewClass<views::WebView>(focused_now)) {
// Focused views could be destroyed. Track what we want to test for when
// OnDidChangeFocus is called.
has_focused_on_non_webview_ = true;
}
}
focus_change_count++;
}
raw_ptr<views::FocusManager> focus_manager_;
bool has_focused_on_non_webview_ = false;
int focus_change_count = 0;
};
// Switching tabs does not focus views unexpectedly.
// (bug http://crbug.com/791757, bug http://crbug.com/777051)
IN_PROC_BROWSER_TEST_F(BrowserViewFocusTest, TabChangesAvoidSpuriousFocus) {
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
ASSERT_TRUE(embedded_test_server()->Start());
// First we navigate to our test page.
GURL url = embedded_test_server()->GetURL(kSimplePage);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Create another tab.
ASSERT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_TYPED));
// Begin recording focus changes.
gfx::NativeWindow window = browser()->window()->GetNativeWindow();
views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
views::FocusManager* focus_manager = widget->GetFocusManager();
FocusedViewClassRecorder focus_change_recorder(focus_manager);
// Switch tabs using ctrl+tab.
ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_TAB, true,
false, false, false));
// Everything that was focused on must be a WebView.
EXPECT_FALSE(focus_change_recorder.GetHasFocusedOnNonWebView());
EXPECT_EQ(focus_change_recorder.GetFocusChangeCount(), 2);
}
class BrowserViewFocusSideBySideTest : public BrowserViewFocusTest {
public:
BrowserViewFocusSideBySideTest() {
scoped_feature_list_.InitAndEnableFeature(features::kSideBySide);
}
void TestSplitTabFocusOrder() {
gfx::NativeWindow window = browser()->window()->GetNativeWindow();
views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
views::FocusManager* focus_manager = widget->GetFocusManager();
std::vector<ContentsContainerView*> contents_container_views =
browser()->GetBrowserView().GetContentsContainerViews();
ASSERT_EQ(2, contents_container_views.size());
// Start from the view prior to the left contents web view in the focus
// order. This should be somewhere outside of the contents container, but
// where it is depends on the platform.
focus_manager->SetFocusedView(contents_container_views[0]->contents_view());
focus_manager->AdvanceFocus(true);
views::View* start_view = focus_manager->GetFocusedView();
ASSERT_FALSE(
browser()->GetBrowserView().contents_container()->Contains(start_view));
// Start advancing focus forwards.
focus_manager->AdvanceFocus(false);
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_EQ(focus_manager->GetFocusedView(),
contents_container_views[0]->contents_view());
focus_manager->AdvanceFocus(false);
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_TRUE(contents_container_views[0]->mini_toolbar()->Contains(
focus_manager->GetFocusedView()));
focus_manager->AdvanceFocus(false);
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_TRUE(browser()
->GetBrowserView()
.multi_contents_view()
->resize_area_for_testing()
->Contains(focus_manager->GetFocusedView()));
focus_manager->AdvanceFocus(false);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_EQ(focus_manager->GetFocusedView(),
contents_container_views[1]->contents_view());
focus_manager->AdvanceFocus(false);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_TRUE(contents_container_views[1]->mini_toolbar()->Contains(
focus_manager->GetFocusedView()));
// Focus has advanced past the right tab's mini toolbar button. This should
// be somewhere outside of the contents container, but where it is depends
// on the platform.
focus_manager->AdvanceFocus(false);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
ASSERT_FALSE(browser()->GetBrowserView().contents_container()->Contains(
focus_manager->GetFocusedView()));
// Start advancing focus backwards.
focus_manager->AdvanceFocus(true);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_TRUE(contents_container_views[1]->mini_toolbar()->Contains(
focus_manager->GetFocusedView()));
focus_manager->AdvanceFocus(true);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_EQ(focus_manager->GetFocusedView(),
contents_container_views[1]->contents_view());
focus_manager->AdvanceFocus(true);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_TRUE(browser()
->GetBrowserView()
.multi_contents_view()
->resize_area_for_testing()
->Contains(focus_manager->GetFocusedView()));
focus_manager->AdvanceFocus(true);
// The right tab is still focused here, because we entered the left tab's
// mini toolbar in reverse, so we have not yet focused the left contents web
// view.
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_TRUE(contents_container_views[0]->mini_toolbar()->Contains(
focus_manager->GetFocusedView()));
focus_manager->AdvanceFocus(true);
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_EQ(focus_manager->GetFocusedView(),
contents_container_views[0]->contents_view());
focus_manager->AdvanceFocus(true);
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_EQ(start_view, focus_manager->GetFocusedView());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(BrowserViewFocusSideBySideTest, FocusOrder) {
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
ASSERT_TRUE(embedded_test_server()->Start());
// Create a split tab.
GURL url = GURL(url::kAboutBlankURL);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_TYPED));
browser()->tab_strip_model()->AddToNewSplit(
{0}, split_tabs::SplitTabVisualData(),
split_tabs::SplitTabCreatedSource::kToolbarButton);
ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
TestSplitTabFocusOrder();
}
// Tests that when we activate and then close the right tab in a split, focus
// order is preserved. Previously there was a bug where this would cause the
// left/right containers' order in the view hierarchy to be reversed.
IN_PROC_BROWSER_TEST_F(BrowserViewFocusSideBySideTest,
FocusOrderAfterClosingRightTab) {
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
ASSERT_TRUE(embedded_test_server()->Start());
// Create a split tab.
GURL url = GURL(url::kAboutBlankURL);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_TYPED));
browser()->tab_strip_model()->AddToNewSplit(
{0}, split_tabs::SplitTabVisualData(),
split_tabs::SplitTabCreatedSource::kToolbarButton);
ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
// Close the right tab, then create another split.
browser()->tab_strip_model()->CloseWebContentsAt(1,
TabCloseTypes::CLOSE_NONE);
ASSERT_EQ(1, browser()->tab_strip_model()->GetTabCount());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_TYPED));
browser()->tab_strip_model()->AddToNewSplit(
{0}, split_tabs::SplitTabVisualData(),
split_tabs::SplitTabCreatedSource::kToolbarButton);
ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
TestSplitTabFocusOrder();
}