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.
5 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/ui/views/profiles/avatar_menu_button.h"
11 #include "chrome/browser/ui/views/tab_icon_view.h"
12 #include "chrome/browser/ui/views/tabs/tab.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "components/signin/core/common/profile_management_switches.h"
15 #include "ui/gfx/image/image_skia.h"
16 #include "ui/gfx/image/image_skia_rep.h"
17 #include "ui/gfx/text_constants.h"
18 #include "ui/views/controls/button/image_button.h"
19 #include "ui/views/controls/button/menu_button.h"
20 #include "ui/views/controls/label.h"
21 #include "ui/views/test/views_test_base.h"
23 #if defined(ENABLE_MANAGED_USERS)
24 #include "chrome/browser/ui/views/profiles/supervised_user_avatar_label.h"
31 const int kWidth = 500;
33 class TestLayoutDelegate : public OpaqueBrowserFrameViewLayoutDelegate {
43 : show_avatar_(false),
44 show_caption_buttons_(true),
45 window_state_(STATE_NORMAL) {
48 ~TestLayoutDelegate() override {}
50 void SetWindowTitle(const base::string16& title) {
51 window_title_ = title;
54 void SetShouldShowAvatar(bool show_avatar) {
55 show_avatar_ = show_avatar;
58 void SetShouldShowCaptionButtons(bool show_caption_buttons) {
59 show_caption_buttons_ = show_caption_buttons;
62 void SetWindowState(WindowState state) {
63 window_state_ = state;
66 // OpaqueBrowserFrameViewLayoutDelegate overrides:
68 bool ShouldShowWindowIcon() const override { return !window_title_.empty(); }
70 bool ShouldShowWindowTitle() const override { return !window_title_.empty(); }
72 base::string16 GetWindowTitle() const override { return window_title_; }
74 int GetIconSize() const override {
75 // The value on linux_aura and non-aura windows.
79 bool ShouldLeaveOffsetNearTopBorder() const override {
80 return !IsMaximized();
83 gfx::Size GetBrowserViewMinimumSize() const override {
84 // Taken from a calculation in BrowserViewLayout.
85 return gfx::Size(168, 64);
88 bool ShouldShowCaptionButtons() const override {
89 return show_caption_buttons_;
92 bool ShouldShowAvatar() const override { return show_avatar_; }
94 bool IsRegularOrGuestSession() const override { return true; }
96 gfx::ImageSkia GetOTRAvatarIcon() const override {
97 // The calculations depend on the size of the OTR resource, and chromeos
98 // uses a different sized image, so hard code the size of the current
100 gfx::ImageSkiaRep rep(gfx::Size(40, 29), 1.0f);
101 gfx::ImageSkia image(rep);
105 bool IsMaximized() const override { return window_state_ == STATE_MAXIMIZED; }
107 bool IsMinimized() const override { return window_state_ == STATE_MINIMIZED; }
109 bool IsFullscreen() const override {
110 return window_state_ == STATE_FULLSCREEN;
113 bool IsTabStripVisible() const override { return window_title_.empty(); }
115 int GetTabStripHeight() const override {
116 return IsTabStripVisible() ? Tab::GetMinimumUnselectedSize().height() : 0;
119 gfx::Size GetTabstripPreferredSize() const override {
120 // Measured from Tabstrip::GetPreferredSize().
121 return IsTabStripVisible() ? gfx::Size(78, 29) : gfx::Size(0, 0);
125 base::string16 window_title_;
127 bool show_caption_buttons_;
128 WindowState window_state_;
130 DISALLOW_COPY_AND_ASSIGN(TestLayoutDelegate);
135 class OpaqueBrowserFrameViewLayoutTest : public views::ViewsTestBase {
137 OpaqueBrowserFrameViewLayoutTest() {}
138 ~OpaqueBrowserFrameViewLayoutTest() override {}
140 void SetUp() override {
141 views::ViewsTestBase::SetUp();
143 delegate_.reset(new TestLayoutDelegate);
144 layout_manager_ = new OpaqueBrowserFrameViewLayout(delegate_.get());
145 layout_manager_->set_extra_caption_y(0);
146 layout_manager_->set_window_caption_spacing(0);
147 widget_ = new Widget;
148 widget_->Init(CreateParams(Widget::InitParams::TYPE_POPUP));
149 root_view_ = widget_->GetRootView();
150 root_view_->SetSize(gfx::Size(kWidth, kWidth));
151 root_view_->SetLayoutManager(layout_manager_);
153 // Add the caption buttons. We use fake images because we're modeling the
154 // Windows assets here, while the linux version uses differently sized
157 // TODO(erg): In a follow up patch, separate these sizes out into virtual
158 // accessors so we can test both the windows and linux behaviours once we
159 // start modifying the code.
160 minimize_button_ = InitWindowCaptionButton(
161 VIEW_ID_MINIMIZE_BUTTON, gfx::Size(26, 18));
162 maximize_button_ = InitWindowCaptionButton(
163 VIEW_ID_MAXIMIZE_BUTTON, gfx::Size(25, 18));
164 restore_button_ = InitWindowCaptionButton(
165 VIEW_ID_RESTORE_BUTTON, gfx::Size(25, 18));
166 close_button_ = InitWindowCaptionButton(
167 VIEW_ID_CLOSE_BUTTON, gfx::Size(43, 18));
170 void TearDown() override {
173 views::ViewsTestBase::TearDown();
177 views::ImageButton* InitWindowCaptionButton(ViewID view_id,
178 const gfx::Size& size) {
179 views::ImageButton* button = new views::ImageButton(NULL);
180 gfx::ImageSkiaRep rep(size, 1.0f);
181 gfx::ImageSkia image(rep);
182 button->SetImage(views::CustomButton::STATE_NORMAL, &image);
183 button->set_id(view_id);
184 root_view_->AddChildView(button);
188 void AddWindowTitleIcons() {
189 tab_icon_view_ = new TabIconView(NULL, NULL);
190 tab_icon_view_->set_is_light(true);
191 tab_icon_view_->set_id(VIEW_ID_WINDOW_ICON);
192 root_view_->AddChildView(tab_icon_view_);
194 window_title_ = new views::Label(delegate_->GetWindowTitle());
195 window_title_->SetVisible(delegate_->ShouldShowWindowTitle());
196 window_title_->SetEnabledColor(SK_ColorWHITE);
197 window_title_->SetSubpixelRenderingEnabled(false);
198 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
199 window_title_->set_id(VIEW_ID_WINDOW_TITLE);
200 root_view_->AddChildView(window_title_);
203 void AddAvatarButton() {
204 // Disable the New Avatar Menu.
205 switches::DisableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
207 menu_button_ = new AvatarMenuButton(NULL, false);
208 menu_button_->set_id(VIEW_ID_AVATAR_BUTTON);
209 delegate_->SetShouldShowAvatar(true);
210 root_view_->AddChildView(menu_button_);
213 #if defined(ENABLE_MANAGED_USERS)
214 void AddSupervisedUserAvatarLabel() {
215 supervised_user_avatar_label_ = new SupervisedUserAvatarLabel(NULL);
216 supervised_user_avatar_label_->set_id(VIEW_ID_SUPERVISED_USER_AVATAR_LABEL);
217 root_view_->AddChildView(supervised_user_avatar_label_);
219 // The avatar label should only be used together with the avatar button.
224 void AddNewAvatarButton() {
225 // Enable the New Avatar Menu.
226 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
229 new views::MenuButton(NULL, base::string16(), NULL, false);
230 new_avatar_button_->set_id(VIEW_ID_NEW_AVATAR_BUTTON);
231 root_view_->AddChildView(new_avatar_button_);
234 void ExpectBasicWindowBounds() {
235 EXPECT_EQ("428,1 25x18", maximize_button_->bounds().ToString());
236 EXPECT_EQ("402,1 26x18", minimize_button_->bounds().ToString());
237 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
238 EXPECT_EQ("453,1 43x18", close_button_->bounds().ToString());
242 views::View* root_view_;
243 OpaqueBrowserFrameViewLayout* layout_manager_;
244 scoped_ptr<TestLayoutDelegate> delegate_;
247 views::ImageButton* minimize_button_;
248 views::ImageButton* maximize_button_;
249 views::ImageButton* restore_button_;
250 views::ImageButton* close_button_;
252 TabIconView* tab_icon_view_;
253 views::Label* window_title_;
255 #if defined(ENABLE_MANAGED_USERS)
256 SupervisedUserAvatarLabel* supervised_user_avatar_label_;
258 AvatarMenuButton* menu_button_;
259 views::MenuButton* new_avatar_button_;
261 DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLayoutTest);
264 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindow) {
265 // Tests the layout of a default chrome window with no avatars, no window
266 // titles, and a tabstrip.
267 root_view_->Layout();
269 ExpectBasicWindowBounds();
271 // After some visual inspection, it really does look like the tabstrip is
272 // initally positioned out of our view.
273 EXPECT_EQ("-1,13 398x29",
274 layout_manager_->GetBoundsForTabStrip(
275 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
276 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
278 // A normal window with no window icon still produces icon bounds for
279 // Windows, which has a hidden icon that a user can double click on to close
281 EXPECT_EQ("6,4 17x17", layout_manager_->IconBounds().ToString());
284 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindowMaximized) {
285 // Tests the layout of a default chrome window with no avatars, no window
286 // titles, and a tabstrip, but maximized this time.
287 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
288 root_view_->Layout();
290 // Note how the bounds start at the exact top of the window while maximized
291 // while they start 1 pixel below when unmaximized.
292 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
293 EXPECT_EQ("403,0 26x18", minimize_button_->bounds().ToString());
294 EXPECT_EQ("429,0 25x18", restore_button_->bounds().ToString());
295 EXPECT_EQ("454,0 46x18", close_button_->bounds().ToString());
297 EXPECT_EQ("-5,-3 392x29",
298 layout_manager_->GetBoundsForTabStrip(
299 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
300 EXPECT_EQ("262x61", layout_manager_->GetMinimumSize(kWidth).ToString());
302 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
303 // this rect, extended to the top left corner of the window.
304 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
307 TEST_F(OpaqueBrowserFrameViewLayoutTest, MaximizedWithYOffset) {
308 // Tests the layout of a basic chrome window with the caption buttons slightly
309 // offset from the top of the screen (as they are on Linux).
310 layout_manager_->set_extra_caption_y(2);
311 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
312 root_view_->Layout();
314 // Note how the bounds start at the exact top of the window, DESPITE the
315 // caption Y offset of 2. This ensures that we obey Fitts' Law (the buttons
316 // are clickable on the top edge of the screen). However, the buttons are 2
317 // pixels taller, so the images appear to be offset by 2 pixels.
318 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
319 EXPECT_EQ("403,0 26x20", minimize_button_->bounds().ToString());
320 EXPECT_EQ("429,0 25x20", restore_button_->bounds().ToString());
321 EXPECT_EQ("454,0 46x20", close_button_->bounds().ToString());
323 EXPECT_EQ("-5,-3 392x29",
324 layout_manager_->GetBoundsForTabStrip(
325 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
326 EXPECT_EQ("262x61", layout_manager_->GetMinimumSize(kWidth).ToString());
328 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
329 // this rect, extended to the top left corner of the window.
330 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
333 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowButtonsOnLeft) {
334 // Tests the layout of a chrome window with caption buttons on the left.
335 std::vector<views::FrameButton> leading_buttons;
336 std::vector<views::FrameButton> trailing_buttons;
337 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
338 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
339 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
340 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
341 root_view_->Layout();
343 EXPECT_EQ("73,1 25x18", maximize_button_->bounds().ToString());
344 EXPECT_EQ("47,1 26x18", minimize_button_->bounds().ToString());
345 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
346 EXPECT_EQ("4,1 43x18", close_button_->bounds().ToString());
348 EXPECT_EQ("93,13 398x29",
349 layout_manager_->GetBoundsForTabStrip(
350 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
351 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
353 // If the buttons are on the left, there should be no hidden icon for the user
355 EXPECT_EQ("0,0 0x0", layout_manager_->IconBounds().ToString());
358 TEST_F(OpaqueBrowserFrameViewLayoutTest, WithoutCaptionButtons) {
359 // Tests the layout of a default chrome window with no caption buttons (which
360 // should force the tab strip to be condensed).
361 delegate_->SetShouldShowCaptionButtons(false);
362 root_view_->Layout();
364 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
365 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
366 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
367 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
369 EXPECT_EQ("-5,-3 500x29",
370 layout_manager_->GetBoundsForTabStrip(
371 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
372 EXPECT_EQ("251x61", layout_manager_->GetMinimumSize(kWidth).ToString());
374 // A normal window with no window icon still produces icon bounds for
375 // Windows, which has a hidden icon that a user can double click on to close
377 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
380 TEST_F(OpaqueBrowserFrameViewLayoutTest, MaximizedWithoutCaptionButtons) {
381 // Tests the layout of a maximized chrome window with no caption buttons.
382 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
383 delegate_->SetShouldShowCaptionButtons(false);
384 root_view_->Layout();
386 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
387 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
388 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
389 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
391 EXPECT_EQ("-5,-3 500x29",
392 layout_manager_->GetBoundsForTabStrip(
393 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
394 EXPECT_EQ("251x61", layout_manager_->GetMinimumSize(kWidth).ToString());
396 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
397 // this rect, extended to the top left corner of the window.
398 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
401 TEST_F(OpaqueBrowserFrameViewLayoutTest, WithWindowTitleAndIcon) {
402 // Tests the layout of pop up windows.
403 delegate_->SetWindowTitle(base::ASCIIToUTF16("Window Title"));
404 AddWindowTitleIcons();
405 root_view_->Layout();
407 // We should have the right hand side should match the BasicWindow case.
408 ExpectBasicWindowBounds();
410 // Check the location of the tab icon and window title.
411 EXPECT_EQ("6,3 17x17", tab_icon_view_->bounds().ToString());
412 EXPECT_EQ("27,3 370x17", window_title_->bounds().ToString());
415 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatar) {
416 // Tests a normal tabstrip window with an avatar icon.
418 root_view_->Layout();
420 ExpectBasicWindowBounds();
422 // Check the location of the avatar
423 EXPECT_EQ("7,11 40x29", menu_button_->bounds().ToString());
424 EXPECT_EQ("45,13 352x29",
425 layout_manager_->GetBoundsForTabStrip(
426 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
427 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
430 TEST_F(OpaqueBrowserFrameViewLayoutTest,
431 WindowWithAvatarWithoutCaptionButtonsOnLeft) {
432 // Tests the layout of a chrome window with an avatar icon and no caption
433 // buttons. However, the caption buttons *would* be on the left if they
434 // weren't hidden, and therefore, the avatar icon should be on the right.
435 // The lack of caption buttons should force the tab strip to be condensed.
437 std::vector<views::FrameButton> leading_buttons;
438 std::vector<views::FrameButton> trailing_buttons;
439 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
440 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
441 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
442 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
443 delegate_->SetShouldShowCaptionButtons(false);
444 root_view_->Layout();
446 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
447 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
448 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
449 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
451 // Check the location of the avatar
452 EXPECT_EQ("458,0 40x24", menu_button_->bounds().ToString());
453 EXPECT_EQ("-5,-3 458x29",
454 layout_manager_->GetBoundsForTabStrip(
455 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
456 EXPECT_EQ("251x61", layout_manager_->GetMinimumSize(kWidth).ToString());
458 // A normal window with no window icon still produces icon bounds for
459 // Windows, which has a hidden icon that a user can double click on to close
461 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
464 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithNewAvatar) {
465 // Tests a normal tabstrip window with the new style avatar icon.
466 AddNewAvatarButton();
467 root_view_->Layout();
469 ExpectBasicWindowBounds();
471 // Check the location of the avatar button.
472 EXPECT_EQ("385,1 12x18", new_avatar_button_->bounds().ToString());
473 // The new tab button is 39px wide and slides completely under the new
474 // avatar button, thus increasing the tabstrip by that amount.
475 EXPECT_EQ("-1,13 420x29",
476 layout_manager_->GetBoundsForTabStrip(
477 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
478 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
481 #if defined(ENABLE_MANAGED_USERS)
482 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarWithButtonsOnLeft) {
483 // Tests the layout of a chrome window with an avatar icon and caption buttons
484 // on the left. The avatar icon should therefore be on the right.
485 // AddAvatarLabel() also adds the avatar button.
486 AddSupervisedUserAvatarLabel();
487 std::vector<views::FrameButton> leading_buttons;
488 std::vector<views::FrameButton> trailing_buttons;
489 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
490 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
491 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
492 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
493 root_view_->Layout();
495 EXPECT_EQ("73,1 25x18", maximize_button_->bounds().ToString());
496 EXPECT_EQ("47,1 26x18", minimize_button_->bounds().ToString());
497 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
498 EXPECT_EQ("4,1 43x18", close_button_->bounds().ToString());
500 // Check the location of the avatar
501 EXPECT_EQ("454,11 40x29", menu_button_->bounds().ToString());
503 // Check the tab strip bounds.
504 gfx::Rect tab_strip_bounds = layout_manager_->GetBoundsForTabStrip(
505 delegate_->GetTabstripPreferredSize(), kWidth);
506 EXPECT_GT(tab_strip_bounds.x(), maximize_button_->bounds().x());
507 EXPECT_GT(maximize_button_->bounds().right(), tab_strip_bounds.x());
508 EXPECT_EQ(13, tab_strip_bounds.y());
509 EXPECT_EQ(29, tab_strip_bounds.height());
510 EXPECT_GT(supervised_user_avatar_label_->bounds().x(),
511 tab_strip_bounds.right());
512 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
514 // Check the relative location of the avatar label to the avatar. The right
515 // end of the avatar label should be slightly to the right of the right end of
517 EXPECT_GT(supervised_user_avatar_label_->bounds().right(),
518 menu_button_->bounds().right());
519 EXPECT_GT(menu_button_->bounds().x(),
520 supervised_user_avatar_label_->bounds().x());
521 EXPECT_GT(menu_button_->bounds().bottom(),
522 supervised_user_avatar_label_->bounds().bottom());
523 EXPECT_GT(supervised_user_avatar_label_->bounds().y(),
524 menu_button_->bounds().y());
526 // This means that the menu will pop out facing the left (if it were to face
527 // the right, it would go outside the window frame and be clipped).
528 EXPECT_TRUE(menu_button_->button_on_right());
530 // If the buttons are on the left, there should be no hidden icon for the user
532 EXPECT_EQ("0,0 0x0", layout_manager_->IconBounds().ToString());
535 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarLabelAndButtonOnLeft) {
536 AddSupervisedUserAvatarLabel();
537 root_view_->Layout();
539 ExpectBasicWindowBounds();
541 // Check the location of the avatar label relative to the avatar button if
542 // both are displayed on the left side.
543 // The label height and width depends on the font size and the text displayed.
544 // This may possibly change, so we don't test it here.
545 EXPECT_EQ(menu_button_->bounds().x() - 2,
546 supervised_user_avatar_label_->bounds().x());
547 EXPECT_EQ(menu_button_->bounds().bottom() - 3 -
548 supervised_user_avatar_label_->bounds().height(),
549 supervised_user_avatar_label_->bounds().y());