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