blob: ffe0a84eba181a1d31fb1607a6828a3e1f79400d [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 "chrome/browser/ui/views/frame/browser_view_layout.h"
#include <memory>
#include "base/containers/fixed_flat_set.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/browser_view_layout_delegate.h"
#include "chrome/browser/ui/views/frame/contents_layout_manager.h"
#include "chrome/browser/ui/views/frame/mock_immersive_mode_controller.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/infobars/infobar_container_view.h"
#include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/unowned_user_data/unowned_user_data_host.h"
#include "ui/views/controls/separator.h"
#include "ui/views/test/views_test_utils.h"
namespace {
// Save space for the separator.
constexpr int kToolbarHeight = 30 - views::Separator::kThickness;
constexpr int kBaseWidth = 800;
constexpr gfx::Size kDefaultViewSize = gfx::Size(kBaseWidth, 600);
class MockBrowserViewLayoutDelegate : public BrowserViewLayoutDelegate {
public:
explicit MockBrowserViewLayoutDelegate(
const ImmersiveModeController* immersive_mode_controller)
: immersive_mode_controller_(immersive_mode_controller) {}
MockBrowserViewLayoutDelegate(const MockBrowserViewLayoutDelegate&) = delete;
MockBrowserViewLayoutDelegate& operator=(
const MockBrowserViewLayoutDelegate&) = delete;
~MockBrowserViewLayoutDelegate() override = default;
void set_should_draw_tab_strip(bool visible) {
should_draw_tab_strip_ = visible;
}
void set_toolbar_visible(bool visible) { toolbar_visible_ = visible; }
void set_bookmark_bar_visible(bool visible) {
bookmark_bar_visible_ = visible;
}
void set_content_separator_enabled(bool visible) {
content_separator_enabled_ = visible;
}
void set_top_controls_slide_enabled(bool enabled) {
top_controls_slide_enabled_ = enabled;
}
void set_top_controls_shown_ratio(float ratio) {
top_controls_shown_ratio_ = ratio;
}
// BrowserViewLayout::Delegate overrides:
bool ShouldDrawTabStrip() const override { return should_draw_tab_strip_; }
bool GetBorderlessModeEnabled() const override { return false; }
gfx::Rect GetBoundsForTabStripRegionInBrowserView() const override {
return gfx::Rect();
}
gfx::Rect GetBoundsForToolbarInVerticalTabBrowserView() const override {
return gfx::Rect();
}
gfx::Rect GetBoundsForWebAppFrameToolbarInBrowserView() const override {
return gfx::Rect();
}
void LayoutWebAppWindowTitle(
const gfx::Rect& available_space,
views::Label& window_title_label) const override {}
int GetTopInsetInBrowserView() const override { return 0; }
bool IsToolbarVisible() const override { return toolbar_visible_; }
bool IsBookmarkBarVisible() const override { return bookmark_bar_visible_; }
bool IsContentsSeparatorEnabled() const override {
return content_separator_enabled_;
}
bool IsActiveTabSplit() const override { return false; }
const ImmersiveModeController* GetImmersiveModeController() const override {
return immersive_mode_controller_.get();
}
ExclusiveAccessBubbleViews* GetExclusiveAccessBubble() const override {
return nullptr;
}
bool IsTopControlsSlideBehaviorEnabled() const override {
return top_controls_slide_enabled_;
}
float GetTopControlsSlideBehaviorShownRatio() const override {
return top_controls_shown_ratio_;
}
bool SupportsWindowFeature(
const Browser::WindowFeature feature) const override {
static constexpr auto kSupportedFeatures =
base::MakeFixedFlatSet<Browser::WindowFeature>({
Browser::FEATURE_TABSTRIP,
Browser::FEATURE_TOOLBAR,
Browser::FEATURE_LOCATIONBAR,
Browser::FEATURE_BOOKMARKBAR,
});
return kSupportedFeatures.contains(feature);
}
gfx::NativeView GetHostViewForAnchoring() const override {
return gfx::NativeView();
}
bool HasFindBarController() const override { return false; }
void MoveWindowForFindBarIfNecessary() const override {}
bool IsWindowControlsOverlayEnabled() const override { return false; }
void UpdateWindowControlsOverlay(const gfx::Rect& rect) override {}
bool ShouldLayoutTabStrip() const override { return true; }
int GetExtraInfobarOffset() const override { return 0; }
private:
bool should_draw_tab_strip_ = true;
bool toolbar_visible_ = true;
bool bookmark_bar_visible_ = true;
bool content_separator_enabled_ = true;
bool top_controls_slide_enabled_ = false;
float top_controls_shown_ratio_ = 1.f;
const raw_ptr<const ImmersiveModeController> immersive_mode_controller_;
};
///////////////////////////////////////////////////////////////////////////////
std::unique_ptr<views::View> CreateFixedSizeView(const gfx::Size& size) {
auto view = std::make_unique<views::View>();
view->SetPreferredSize(size);
view->SizeToPreferredSize();
return view;
}
} // anonymous namespace
///////////////////////////////////////////////////////////////////////////////
// Tests of BrowserViewLayout. Runs tests without constructing a BrowserView.
class BrowserViewLayoutTest : public ChromeViewsTestBase {
public:
BrowserViewLayoutTest()
: delegate_(nullptr),
top_container_(nullptr),
tab_strip_region_view_(nullptr),
toolbar_(nullptr),
infobar_container_(nullptr),
contents_container_(nullptr),
contents_web_view_(nullptr),
devtools_web_view_(nullptr) {}
BrowserViewLayoutTest(const BrowserViewLayoutTest&) = delete;
BrowserViewLayoutTest& operator=(const BrowserViewLayoutTest&) = delete;
~BrowserViewLayoutTest() override = default;
BrowserViewLayout* layout() { return layout_; }
MockBrowserViewLayoutDelegate* delegate() { return delegate_; }
views::View* browser_view() { return browser_view_.get(); }
views::View* top_container() { return top_container_; }
TabStrip* tab_strip() { return tab_strip_region_view_->tab_strip(); }
views::View* webui_tab_strip() { return webui_tab_strip_; }
views::View* toolbar() { return toolbar_; }
views::View* separator() { return separator_; }
InfoBarContainerView* infobar_container() { return infobar_container_; }
views::View* contents_container() { return contents_container_; }
views::View* browser_main_view() { return main_container_; }
void SetUp() override {
ChromeViewsTestBase::SetUp();
browser_view_ = CreateFixedSizeView(kDefaultViewSize);
EXPECT_CALL(browser_window_interface_, GetUnownedUserDataHost)
.WillRepeatedly(testing::ReturnRef(data_host_));
immersive_mode_controller_ = std::make_unique<MockImmersiveModeController>(
&browser_window_interface_);
main_region_ =
browser_view_->AddChildView(CreateFixedSizeView(kDefaultViewSize));
main_container_ =
main_region_->AddChildView(CreateFixedSizeView(kDefaultViewSize));
top_container_ = main_container_->AddChildView(
CreateFixedSizeView(gfx::Size(kBaseWidth, 60)));
auto tab_strip = std::make_unique<TabStrip>(
std::make_unique<FakeBaseTabStripController>());
tab_strip_region_view_ = top_container_->AddChildView(
std::make_unique<TabStripRegionView>(std::move(tab_strip)));
webui_tab_strip_ = top_container_->AddChildView(
CreateFixedSizeView(gfx::Size(kBaseWidth, 200)));
webui_tab_strip_->SetVisible(false);
toolbar_ = top_container_->AddChildView(
CreateFixedSizeView(gfx::Size(kBaseWidth, kToolbarHeight)));
separator_ =
top_container_->AddChildView(std::make_unique<views::Separator>());
infobar_container_ = browser_view_->AddChildView(
std::make_unique<InfoBarContainerView>(nullptr));
left_aligned_side_panel_separator_ =
main_container_->AddChildView(std::make_unique<views::Separator>());
right_aligned_side_panel_separator_ =
main_container_->AddChildView(std::make_unique<views::Separator>());
side_panel_rounded_corner_ =
main_container_->AddChildView(CreateFixedSizeView(gfx::Size(16, 16)));
contents_container_ =
main_container_->AddChildView(CreateFixedSizeView(kDefaultViewSize));
devtools_web_view_ = contents_container_->AddChildView(
CreateFixedSizeView(kDefaultViewSize));
devtools_web_view_->SetVisible(false);
devtools_scrim_view_ = contents_container_->AddChildView(
CreateFixedSizeView(kDefaultViewSize));
devtools_scrim_view_->SetVisible(false);
contents_web_view_ = contents_container_->AddChildView(
CreateFixedSizeView(kDefaultViewSize));
contents_scrim_view_ = contents_container_->AddChildView(
CreateFixedSizeView(kDefaultViewSize));
lens_overlay_view_ = contents_container_->AddChildView(
CreateFixedSizeView(kDefaultViewSize));
contents_container_->SetLayoutManager(
std::make_unique<ContentsLayoutManager>(contents_web_view_,
lens_overlay_view_));
auto delegate = std::make_unique<MockBrowserViewLayoutDelegate>(
immersive_mode_controller_.get());
delegate_ = delegate.get();
BrowserViewLayoutViews layout_views;
layout_views.main_region = main_region_;
layout_views.main_container = main_container_;
layout_views.top_container = top_container_;
layout_views.tab_strip_region_view = tab_strip_region_view_;
layout_views.toolbar = toolbar_;
layout_views.infobar_container = infobar_container_;
layout_views.contents_container = contents_container_;
layout_views.left_aligned_side_panel_separator =
left_aligned_side_panel_separator_;
layout_views.right_aligned_side_panel_separator =
right_aligned_side_panel_separator_;
layout_views.side_panel_rounded_corner = side_panel_rounded_corner_;
layout_views.top_container_separator = separator_;
auto layout = BrowserViewLayout::CreateLayout(std::move(delegate), nullptr,
std::move(layout_views));
layout->set_webui_tab_strip(webui_tab_strip());
layout_ = layout.get();
browser_view_->SetLayoutManager(std::move(layout));
}
void TearDown() override {
// The layout owns the delegate, so we destroy the layout and null out both
// pointers to avoid dangling pointers.
delegate_ = nullptr;
layout_ = nullptr;
browser_view_->SetLayoutManager(nullptr);
// The other views are children of |browser_view_| and will be destroyed
// along with it after TearDown(). Null out the pointers to avoid them
// dangling.
main_region_ = nullptr;
main_container_ = nullptr;
top_container_ = nullptr;
webui_tab_strip_ = nullptr;
toolbar_ = nullptr;
separator_ = nullptr;
infobar_container_ = nullptr;
left_aligned_side_panel_separator_ = nullptr;
right_aligned_side_panel_separator_ = nullptr;
side_panel_rounded_corner_ = nullptr;
contents_container_ = nullptr;
contents_web_view_ = nullptr;
devtools_web_view_ = nullptr;
devtools_scrim_view_ = nullptr;
contents_scrim_view_ = nullptr;
lens_overlay_view_ = nullptr;
ChromeViewsTestBase::TearDown();
}
// For the purposes of this test, boolean values are directly set on a
// BrowserViewLayoutDelegate which are checked during layout or child view
// visibility is directly changed. These calls do not schedule a layout and we
// need to manually invalidate layout.
void InvalidateAndRunScheduledLayoutOnBrowserView() {
browser_view()->InvalidateLayout();
views::test::RunScheduledLayout(browser_view());
}
private:
raw_ptr<BrowserViewLayout> layout_;
raw_ptr<MockBrowserViewLayoutDelegate> delegate_; // Owned by |layout_|.
std::unique_ptr<views::View> browser_view_;
// Views owned by |browser_view_|.
raw_ptr<views::View> main_region_;
raw_ptr<views::View> top_container_;
raw_ptr<TabStripRegionView> tab_strip_region_view_;
raw_ptr<views::View> webui_tab_strip_;
raw_ptr<views::View> toolbar_;
raw_ptr<views::Separator> separator_;
raw_ptr<InfoBarContainerView> infobar_container_;
raw_ptr<views::View> main_container_;
raw_ptr<views::View> left_aligned_side_panel_separator_;
raw_ptr<views::View> right_aligned_side_panel_separator_;
raw_ptr<views::View> side_panel_rounded_corner_;
raw_ptr<views::View> contents_container_;
raw_ptr<views::View> contents_web_view_;
raw_ptr<views::View> devtools_web_view_;
raw_ptr<views::View> devtools_scrim_view_;
raw_ptr<views::View> contents_scrim_view_;
raw_ptr<views::View> lens_overlay_view_;
ui::UnownedUserDataHost data_host_;
MockBrowserWindowInterface browser_window_interface_;
std::unique_ptr<MockImmersiveModeController> immersive_mode_controller_;
};
// Test basic construction and initialization.
TEST_F(BrowserViewLayoutTest, BrowserViewLayout) {
EXPECT_TRUE(layout()->GetWebContentsModalDialogHost());
EXPECT_FALSE(layout()->IsInfobarVisibleForTesting());
}
// Test the core layout functions.
TEST_F(BrowserViewLayoutTest, Layout) {
// Simulate a window with no interesting UI.
delegate()->set_should_draw_tab_strip(false);
delegate()->set_toolbar_visible(false);
delegate()->set_bookmark_bar_visible(false);
InvalidateAndRunScheduledLayoutOnBrowserView();
// Top views are zero-height.
EXPECT_EQ(gfx::Rect(0, 0, 0, 0), tab_strip()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, 0, 0), toolbar()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, 0, 0), infobar_container()->bounds());
// Contents split fills the window.
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), contents_container()->bounds());
// Turn on the toolbar, like in a pop-up window.
delegate()->set_toolbar_visible(true);
InvalidateAndRunScheduledLayoutOnBrowserView();
// Now the toolbar has bounds and other views shift down.
EXPECT_EQ(gfx::Rect(0, 0, 0, 0), tab_strip()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, kToolbarHeight), toolbar()->bounds());
EXPECT_EQ(
gfx::Rect(0, kToolbarHeight, kBaseWidth, views::Separator::kThickness),
separator()->bounds());
EXPECT_EQ(gfx::Rect(0, 30, 0, 0), infobar_container()->bounds());
// browser_main_view contains the toolbar and separator
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), browser_main_view()->bounds());
EXPECT_EQ(gfx::Rect(0, 30, kBaseWidth, 570), contents_container()->bounds());
// Disable the contents separator.
delegate()->set_content_separator_enabled(false);
InvalidateAndRunScheduledLayoutOnBrowserView();
// Now the separator is not visible and the content grows vertically.
EXPECT_EQ(gfx::Rect(0, 0, 0, 0), tab_strip()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, kToolbarHeight), toolbar()->bounds());
EXPECT_FALSE(separator()->GetVisible());
EXPECT_EQ(gfx::Rect(0, 29, 0, 0), infobar_container()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), browser_main_view()->bounds());
EXPECT_EQ(gfx::Rect(0, 29, kBaseWidth, 571), contents_container()->bounds());
// TODO(jamescook): Tab strip and bookmark bar.
}
TEST_F(BrowserViewLayoutTest, LayoutContentsWithTopControlsSlideBehavior) {
// Top controls are fully shown.
delegate()->set_should_draw_tab_strip(false);
delegate()->set_toolbar_visible(true);
delegate()->set_top_controls_slide_enabled(true);
delegate()->set_top_controls_shown_ratio(1.f);
InvalidateAndRunScheduledLayoutOnBrowserView();
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 30), top_container()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, kToolbarHeight), toolbar()->bounds());
EXPECT_EQ(
gfx::Rect(0, kToolbarHeight, kBaseWidth, views::Separator::kThickness),
separator()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), browser_main_view()->bounds());
EXPECT_EQ(gfx::Rect(0, 30, kBaseWidth, 570), contents_container()->bounds());
// Top controls are half shown, half hidden.
delegate()->set_top_controls_shown_ratio(0.5f);
InvalidateAndRunScheduledLayoutOnBrowserView();
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 30), top_container()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, kToolbarHeight), toolbar()->bounds());
EXPECT_EQ(
gfx::Rect(0, kToolbarHeight, kBaseWidth, views::Separator::kThickness),
separator()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), browser_main_view()->bounds());
EXPECT_EQ(gfx::Rect(0, 30, kBaseWidth, 570), contents_container()->bounds());
// Top controls are fully hidden. the contents are expanded in height by an
// amount equal to the top controls height.
delegate()->set_top_controls_shown_ratio(0.f);
InvalidateAndRunScheduledLayoutOnBrowserView();
EXPECT_EQ(gfx::Rect(0, -30, kBaseWidth, 30), top_container()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, kToolbarHeight), toolbar()->bounds());
EXPECT_EQ(
gfx::Rect(0, kToolbarHeight, kBaseWidth, views::Separator::kThickness),
separator()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), browser_main_view()->bounds());
EXPECT_EQ(gfx::Rect(0, 0, kBaseWidth, 600), contents_container()->bounds());
}
TEST_F(BrowserViewLayoutTest, WebUITabStripPushesDownContents) {
delegate()->set_should_draw_tab_strip(false);
delegate()->set_toolbar_visible(true);
webui_tab_strip()->SetVisible(false);
InvalidateAndRunScheduledLayoutOnBrowserView();
const gfx::Rect original_contents_bounds = contents_container()->bounds();
EXPECT_EQ(gfx::Size(), webui_tab_strip()->size());
webui_tab_strip()->SetVisible(true);
InvalidateAndRunScheduledLayoutOnBrowserView();
EXPECT_LT(0, webui_tab_strip()->size().height());
EXPECT_EQ(original_contents_bounds.size(), contents_container()->size());
EXPECT_EQ(webui_tab_strip()->size().height(),
contents_container()->bounds().y() - original_contents_bounds.y());
}