| // 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()); |
| } |