1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/frame/browser_view_layout.h"
6 
7 #include <memory>
8 
9 #include "base/containers/flat_set.h"
10 #include "base/macros.h"
11 #include "chrome/browser/ui/views/frame/browser_view.h"
12 #include "chrome/browser/ui/views/frame/browser_view_layout_delegate.h"
13 #include "chrome/browser/ui/views/frame/contents_layout_manager.h"
14 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
15 #include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
16 #include "chrome/browser/ui/views/infobars/infobar_container_view.h"
17 #include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
18 #include "chrome/browser/ui/views/tabs/tab_strip.h"
19 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
20 #include "chrome/test/views/chrome_views_test_base.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 #include "ui/views/controls/separator.h"
23 
24 namespace {
25 
26 // Save space for the separator.
27 constexpr int kToolbarHeight = 30 - views::Separator::kThickness;
28 
29 class MockBrowserViewLayoutDelegate : public BrowserViewLayoutDelegate {
30  public:
31   MockBrowserViewLayoutDelegate() = default;
32   ~MockBrowserViewLayoutDelegate() override = default;
33 
set_tab_strip_visible(bool visible)34   void set_tab_strip_visible(bool visible) {
35     tab_strip_visible_ = visible;
36   }
set_toolbar_visible(bool visible)37   void set_toolbar_visible(bool visible) {
38     toolbar_visible_ = visible;
39   }
set_bookmark_bar_visible(bool visible)40   void set_bookmark_bar_visible(bool visible) {
41     bookmark_bar_visible_ = visible;
42   }
set_content_separator_enabled(bool visible)43   void set_content_separator_enabled(bool visible) {
44     content_separator_enabled_ = visible;
45   }
set_top_controls_slide_enabled(bool enabled)46   void set_top_controls_slide_enabled(bool enabled) {
47     top_controls_slide_enabled_ = enabled;
48   }
set_top_controls_shown_ratio(float ratio)49   void set_top_controls_shown_ratio(float ratio) {
50     top_controls_shown_ratio_ = ratio;
51   }
52 
53   // BrowserViewLayout::Delegate overrides:
IsTabStripVisible() const54   bool IsTabStripVisible() const override { return tab_strip_visible_; }
GetBoundsForTabStripRegionInBrowserView() const55   gfx::Rect GetBoundsForTabStripRegionInBrowserView() const override {
56     return gfx::Rect();
57   }
GetTopInsetInBrowserView() const58   int GetTopInsetInBrowserView() const override { return 0; }
GetThemeBackgroundXInset() const59   int GetThemeBackgroundXInset() const override { return 0; }
IsToolbarVisible() const60   bool IsToolbarVisible() const override { return toolbar_visible_; }
IsBookmarkBarVisible() const61   bool IsBookmarkBarVisible() const override { return bookmark_bar_visible_; }
IsContentsSeparatorEnabled() const62   bool IsContentsSeparatorEnabled() const override {
63     return content_separator_enabled_;
64   }
GetExclusiveAccessBubble() const65   ExclusiveAccessBubbleViews* GetExclusiveAccessBubble() const override {
66     return nullptr;
67   }
IsTopControlsSlideBehaviorEnabled() const68   bool IsTopControlsSlideBehaviorEnabled() const override {
69     return top_controls_slide_enabled_;
70   }
GetTopControlsSlideBehaviorShownRatio() const71   float GetTopControlsSlideBehaviorShownRatio() const override {
72     return top_controls_shown_ratio_;
73   }
SupportsWindowFeature(const Browser::WindowFeature feature) const74   bool SupportsWindowFeature(
75       const Browser::WindowFeature feature) const override {
76     static const base::NoDestructor<base::flat_set<Browser::WindowFeature>>
77         supported_features({
78             Browser::FEATURE_TABSTRIP,
79             Browser::FEATURE_TOOLBAR,
80             Browser::FEATURE_LOCATIONBAR,
81             Browser::FEATURE_BOOKMARKBAR,
82         });
83     return base::Contains(*supported_features, feature);
84   }
GetHostView() const85   gfx::NativeView GetHostView() const override { return nullptr; }
BrowserIsTypeNormal() const86   bool BrowserIsTypeNormal() const override { return true; }
HasFindBarController() const87   bool HasFindBarController() const override { return false; }
MoveWindowForFindBarIfNecessary() const88   void MoveWindowForFindBarIfNecessary() const override {}
89 
90  private:
91   bool tab_strip_visible_ = true;
92   bool toolbar_visible_ = true;
93   bool bookmark_bar_visible_ = true;
94   bool content_separator_enabled_ = true;
95   bool top_controls_slide_enabled_ = false;
96   float top_controls_shown_ratio_ = 1.f;
97 
98   DISALLOW_COPY_AND_ASSIGN(MockBrowserViewLayoutDelegate);
99 };
100 
101 ///////////////////////////////////////////////////////////////////////////////
102 
CreateFixedSizeView(const gfx::Size & size)103 std::unique_ptr<views::View> CreateFixedSizeView(const gfx::Size& size) {
104   auto view = std::make_unique<views::View>();
105   view->SetPreferredSize(size);
106   view->SizeToPreferredSize();
107   return view;
108 }
109 
110 ///////////////////////////////////////////////////////////////////////////////
111 
112 class MockImmersiveModeController : public ImmersiveModeController {
113  public:
114   // ImmersiveModeController overrides:
Init(BrowserView * browser_view)115   void Init(BrowserView* browser_view) override {}
SetEnabled(bool enabled)116   void SetEnabled(bool enabled) override {}
IsEnabled() const117   bool IsEnabled() const override { return false; }
ShouldHideTopViews() const118   bool ShouldHideTopViews() const override { return false; }
IsRevealed() const119   bool IsRevealed() const override { return false; }
GetTopContainerVerticalOffset(const gfx::Size & top_container_size) const120   int GetTopContainerVerticalOffset(
121       const gfx::Size& top_container_size) const override {
122     return 0;
123   }
GetRevealedLock(AnimateReveal animate_reveal)124   ImmersiveRevealedLock* GetRevealedLock(AnimateReveal animate_reveal) override
125       WARN_UNUSED_RESULT {
126     return nullptr;
127   }
OnFindBarVisibleBoundsChanged(const gfx::Rect & new_visible_bounds)128   void OnFindBarVisibleBoundsChanged(
129       const gfx::Rect& new_visible_bounds) override {}
ShouldStayImmersiveAfterExitingFullscreen()130   bool ShouldStayImmersiveAfterExitingFullscreen() override { return true; }
OnWidgetActivationChanged(views::Widget * widget,bool active)131   void OnWidgetActivationChanged(views::Widget* widget, bool active) override {}
132 };
133 
134 }  // anonymous namespace
135 
136 ///////////////////////////////////////////////////////////////////////////////
137 // Tests of BrowserViewLayout. Runs tests without constructing a BrowserView.
138 class BrowserViewLayoutTest : public ChromeViewsTestBase {
139  public:
BrowserViewLayoutTest()140   BrowserViewLayoutTest()
141       : delegate_(nullptr),
142         top_container_(nullptr),
143         tab_strip_(nullptr),
144         toolbar_(nullptr),
145         infobar_container_(nullptr),
146         contents_container_(nullptr),
147         contents_web_view_(nullptr),
148         devtools_web_view_(nullptr) {}
~BrowserViewLayoutTest()149   ~BrowserViewLayoutTest() override {}
150 
layout()151   BrowserViewLayout* layout() { return layout_.get(); }
delegate()152   MockBrowserViewLayoutDelegate* delegate() { return delegate_; }
root_view()153   views::View* root_view() { return root_view_.get(); }
top_container()154   views::View* top_container() { return top_container_; }
tab_strip()155   TabStrip* tab_strip() { return tab_strip_; }
webui_tab_strip()156   views::View* webui_tab_strip() { return webui_tab_strip_; }
toolbar()157   views::View* toolbar() { return toolbar_; }
separator()158   views::View* separator() { return separator_; }
infobar_container()159   InfoBarContainerView* infobar_container() { return infobar_container_; }
contents_container()160   views::View* contents_container() { return contents_container_; }
161 
SetUp()162   void SetUp() override {
163     ChromeViewsTestBase::SetUp();
164 
165     root_view_ = CreateFixedSizeView(gfx::Size(800, 600));
166 
167     immersive_mode_controller_ =
168         std::make_unique<MockImmersiveModeController>();
169 
170     top_container_ =
171         root_view_->AddChildView(CreateFixedSizeView(gfx::Size(800, 60)));
172     auto tab_strip = std::make_unique<TabStrip>(
173         std::make_unique<FakeBaseTabStripController>());
174     tab_strip_ = tab_strip.get();
175     TabStripRegionView* tab_strip_region_view = top_container_->AddChildView(
176         std::make_unique<TabStripRegionView>(std::move(tab_strip)));
177     webui_tab_strip_ =
178         top_container_->AddChildView(CreateFixedSizeView(gfx::Size(800, 200)));
179     webui_tab_strip_->SetVisible(false);
180     toolbar_ = top_container_->AddChildView(
181         CreateFixedSizeView(gfx::Size(800, kToolbarHeight)));
182     separator_ =
183         top_container_->AddChildView(std::make_unique<views::Separator>());
184 
185     infobar_container_ = root_view_->AddChildView(
186         std::make_unique<InfoBarContainerView>(nullptr));
187 
188     contents_container_ =
189         root_view_->AddChildView(CreateFixedSizeView(gfx::Size(800, 600)));
190     devtools_web_view_ = contents_container_->AddChildView(
191         CreateFixedSizeView(gfx::Size(800, 600)));
192     devtools_web_view_->SetVisible(false);
193     contents_web_view_ = contents_container_->AddChildView(
194         CreateFixedSizeView(gfx::Size(800, 600)));
195     contents_container_->SetLayoutManager(
196         std::make_unique<ContentsLayoutManager>(devtools_web_view_,
197                                                 contents_web_view_));
198 
199     // TODO(jamescook): Attach |layout_| to |root_view_|?
200     auto delegate = std::make_unique<MockBrowserViewLayoutDelegate>();
201     delegate_ = delegate.get();
202     layout_ = std::make_unique<BrowserViewLayout>(
203         std::move(delegate),
204         nullptr,  // NativeView.
205         nullptr,  // BrowserView.
206         top_container_, tab_strip_region_view, tab_strip_, toolbar_,
207         infobar_container_, contents_container_, nullptr,  // SidePanel.
208         immersive_mode_controller_.get(), nullptr, separator_);
209     layout_->set_webui_tab_strip(webui_tab_strip());
210   }
211 
212  private:
213   std::unique_ptr<BrowserViewLayout> layout_;
214   MockBrowserViewLayoutDelegate* delegate_;  // Owned by |layout_|.
215   std::unique_ptr<views::View> root_view_;
216 
217   // Views owned by |root_view_|.
218   views::View* top_container_;
219   TabStrip* tab_strip_;
220   views::View* webui_tab_strip_;
221   views::View* toolbar_;
222   views::Separator* separator_;
223   InfoBarContainerView* infobar_container_;
224   views::View* contents_container_;
225   views::View* contents_web_view_;
226   views::View* devtools_web_view_;
227 
228   std::unique_ptr<MockImmersiveModeController> immersive_mode_controller_;
229 
230   DISALLOW_COPY_AND_ASSIGN(BrowserViewLayoutTest);
231 };
232 
233 // Test basic construction and initialization.
TEST_F(BrowserViewLayoutTest,BrowserViewLayout)234 TEST_F(BrowserViewLayoutTest, BrowserViewLayout) {
235   EXPECT_TRUE(layout()->GetWebContentsModalDialogHost());
236   EXPECT_FALSE(layout()->IsInfobarVisible());
237 }
238 
239 // Test the core layout functions.
TEST_F(BrowserViewLayoutTest,Layout)240 TEST_F(BrowserViewLayoutTest, Layout) {
241   // Simulate a window with no interesting UI.
242   delegate()->set_tab_strip_visible(false);
243   delegate()->set_toolbar_visible(false);
244   delegate()->set_bookmark_bar_visible(false);
245   layout()->Layout(root_view());
246 
247   // Top views are zero-height.
248   EXPECT_EQ(gfx::Rect(0, 0, 0, 0), tab_strip()->bounds());
249   EXPECT_EQ(gfx::Rect(0, 0, 800, 0), toolbar()->bounds());
250   EXPECT_EQ(gfx::Rect(0, 0, 800, 0), infobar_container()->bounds());
251   // Contents split fills the window.
252   EXPECT_EQ(gfx::Rect(0, 0, 800, 600), contents_container()->bounds());
253 
254   // Turn on the toolbar, like in a pop-up window.
255   delegate()->set_toolbar_visible(true);
256   layout()->Layout(root_view());
257 
258   // Now the toolbar has bounds and other views shift down.
259   EXPECT_EQ(gfx::Rect(0, 0, 0, 0), tab_strip()->bounds());
260   EXPECT_EQ(gfx::Rect(0, 0, 800, kToolbarHeight), toolbar()->bounds());
261   EXPECT_EQ(gfx::Rect(0, kToolbarHeight, 800, views::Separator::kThickness),
262             separator()->bounds());
263   EXPECT_EQ(gfx::Rect(0, 30, 800, 0), infobar_container()->bounds());
264   EXPECT_EQ(gfx::Rect(0, 30, 800, 570), contents_container()->bounds());
265 
266   // Disable the contents separator.
267   delegate()->set_content_separator_enabled(false);
268   layout()->Layout(root_view());
269 
270   // Now the separator is not visible and the content grows vertically.
271   EXPECT_EQ(gfx::Rect(0, 0, 0, 0), tab_strip()->bounds());
272   EXPECT_EQ(gfx::Rect(0, 0, 800, kToolbarHeight), toolbar()->bounds());
273   EXPECT_FALSE(separator()->GetVisible());
274   EXPECT_EQ(gfx::Rect(0, 29, 800, 0), infobar_container()->bounds());
275   EXPECT_EQ(gfx::Rect(0, 29, 800, 571), contents_container()->bounds());
276 
277   // TODO(jamescook): Tab strip and bookmark bar.
278 }
279 
TEST_F(BrowserViewLayoutTest,LayoutDownloadShelf)280 TEST_F(BrowserViewLayoutTest, LayoutDownloadShelf) {
281   constexpr int kHeight = 50;
282   std::unique_ptr<views::View> download_shelf =
283       CreateFixedSizeView(gfx::Size(800, kHeight));
284   layout()->set_download_shelf(download_shelf.get());
285 
286   // If the download shelf isn't visible, it doesn't move the bottom edge.
287   download_shelf->SetVisible(false);
288   constexpr int kBottom = 500;
289   EXPECT_EQ(kBottom, layout()->LayoutDownloadShelf(kBottom));
290 
291   // A visible download shelf moves the bottom edge up.
292   download_shelf->SetVisible(true);
293   constexpr int kTop = kBottom - kHeight;
294   EXPECT_EQ(kTop, layout()->LayoutDownloadShelf(kBottom));
295   EXPECT_EQ(gfx::Rect(0, kTop, 0, kHeight), download_shelf->bounds());
296 }
297 
TEST_F(BrowserViewLayoutTest,LayoutContentsWithTopControlsSlideBehavior)298 TEST_F(BrowserViewLayoutTest, LayoutContentsWithTopControlsSlideBehavior) {
299   // Top controls are fully shown.
300   delegate()->set_tab_strip_visible(false);
301   delegate()->set_toolbar_visible(true);
302   delegate()->set_top_controls_slide_enabled(true);
303   delegate()->set_top_controls_shown_ratio(1.f);
304   layout()->Layout(root_view());
305   EXPECT_EQ(gfx::Rect(0, 0, 800, 30), top_container()->bounds());
306   EXPECT_EQ(gfx::Rect(0, 0, 800, kToolbarHeight), toolbar()->bounds());
307   EXPECT_EQ(gfx::Rect(0, kToolbarHeight, 800, views::Separator::kThickness),
308             separator()->bounds());
309   EXPECT_EQ(gfx::Rect(0, 30, 800, 570), contents_container()->bounds());
310 
311   // Top controls are half shown, half hidden.
312   delegate()->set_top_controls_shown_ratio(0.5f);
313   layout()->Layout(root_view());
314   EXPECT_EQ(gfx::Rect(0, 0, 800, 30), top_container()->bounds());
315   EXPECT_EQ(gfx::Rect(0, 0, 800, kToolbarHeight), toolbar()->bounds());
316   EXPECT_EQ(gfx::Rect(0, kToolbarHeight, 800, views::Separator::kThickness),
317             separator()->bounds());
318   EXPECT_EQ(gfx::Rect(0, 30, 800, 570), contents_container()->bounds());
319 
320   // Top controls are fully hidden. the contents are expanded in height by an
321   // amount equal to the top controls height.
322   delegate()->set_top_controls_shown_ratio(0.f);
323   layout()->Layout(root_view());
324   EXPECT_EQ(gfx::Rect(0, -30, 800, 30), top_container()->bounds());
325   EXPECT_EQ(gfx::Rect(0, 0, 800, kToolbarHeight), toolbar()->bounds());
326   EXPECT_EQ(gfx::Rect(0, kToolbarHeight, 800, views::Separator::kThickness),
327             separator()->bounds());
328   EXPECT_EQ(gfx::Rect(0, 0, 800, 600), contents_container()->bounds());
329 }
330 
TEST_F(BrowserViewLayoutTest,WebUITabStripPushesDownContents)331 TEST_F(BrowserViewLayoutTest, WebUITabStripPushesDownContents) {
332   delegate()->set_tab_strip_visible(false);
333   delegate()->set_toolbar_visible(true);
334   webui_tab_strip()->SetVisible(false);
335   layout()->Layout(root_view());
336   const gfx::Rect original_contents_bounds = contents_container()->bounds();
337   EXPECT_EQ(gfx::Size(), webui_tab_strip()->size());
338 
339   webui_tab_strip()->SetVisible(true);
340   layout()->Layout(root_view());
341   EXPECT_LT(0, webui_tab_strip()->size().height());
342   EXPECT_EQ(original_contents_bounds.size(), contents_container()->size());
343   EXPECT_EQ(webui_tab_strip()->size().height(),
344             contents_container()->bounds().y() - original_contents_bounds.y());
345 }
346