browser_view_layout.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright 2012 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 "base/observer_list.h"
8#include "chrome/browser/profiles/profile.h"
9#include "chrome/browser/ui/browser_finder.h"
10#include "chrome/browser/ui/find_bar/find_bar.h"
11#include "chrome/browser/ui/find_bar/find_bar_controller.h"
12#include "chrome/browser/ui/search/search_model.h"
13#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
14#include "chrome/browser/ui/views/download/download_shelf_view.h"
15#include "chrome/browser/ui/views/frame/browser_frame.h"
16#include "chrome/browser/ui/views/frame/browser_view.h"
17#include "chrome/browser/ui/views/frame/contents_container.h"
18#include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
19#include "chrome/browser/ui/views/frame/top_container_view.h"
20#include "chrome/browser/ui/views/infobars/infobar_container_view.h"
21#include "chrome/browser/ui/views/tabs/tab_strip.h"
22#include "chrome/browser/ui/views/toolbar_view.h"
23#include "chrome/browser/ui/web_contents_modal_dialog_host.h"
24#include "ui/base/hit_test.h"
25#include "ui/gfx/point.h"
26#include "ui/gfx/scrollbar_size.h"
27#include "ui/gfx/size.h"
28#include "ui/views/controls/single_split_view.h"
29#include "ui/views/controls/webview/webview.h"
30
31using views::View;
32
33namespace {
34
35// The visible height of the shadow above the tabs. Clicks in this area are
36// treated as clicks to the frame, rather than clicks to the tab.
37const int kTabShadowSize = 2;
38// The number of pixels the bookmark bar should overlap the spacer by if the
39// spacer is visible.
40const int kSpacerBookmarkBarOverlap = 1;
41// The number of pixels the metro switcher is offset from the right edge.
42const int kWindowSwitcherOffsetX = 7;
43// The number of pixels the constrained window should overlap the bottom
44// of the omnibox.
45const int kConstrainedWindowOverlap = 3;
46
47// Combines View::ConvertPointToTarget and View::HitTest for a given |point|.
48// Converts |point| from |src| to |dst| and hit tests it against |dst|. The
49// converted |point| can then be retrieved and used for additional tests.
50bool ConvertedHitTest(views::View* src, views::View* dst, gfx::Point* point) {
51  DCHECK(src);
52  DCHECK(dst);
53  DCHECK(point);
54  views::View::ConvertPointToTarget(src, dst, point);
55  return dst->HitTestPoint(*point);
56}
57
58}  // namespace
59
60class BrowserViewLayout::WebContentsModalDialogHostViews
61    : public WebContentsModalDialogHost {
62 public:
63  explicit WebContentsModalDialogHostViews(
64      BrowserViewLayout* browser_view_layout)
65          : browser_view_layout_(browser_view_layout) {
66  }
67
68  void NotifyPositionRequiresUpdate() {
69    FOR_EACH_OBSERVER(WebContentsModalDialogHostObserver,
70                      observer_list_,
71                      OnPositionRequiresUpdate());
72  }
73
74 private:
75  // Center horizontally over the content area, with the top overlapping the
76  // browser chrome.
77  virtual gfx::Point GetDialogPosition(const gfx::Size& size) OVERRIDE {
78    int top_y = browser_view_layout_->web_contents_modal_dialog_top_y_;
79    gfx::Rect content_area =
80        browser_view_layout_->browser_view_->GetClientAreaBounds();
81    int middle_x = content_area.x() + content_area.width() / 2;
82    return gfx::Point(middle_x - size.width() / 2, top_y);
83  }
84
85  // Add/remove observer.
86  virtual void AddObserver(
87      WebContentsModalDialogHostObserver* observer) OVERRIDE {
88    observer_list_.AddObserver(observer);
89  }
90  virtual void RemoveObserver(
91      WebContentsModalDialogHostObserver* observer) OVERRIDE {
92    observer_list_.RemoveObserver(observer);
93  }
94
95  const BrowserViewLayout* browser_view_layout_;
96
97  ObserverList<WebContentsModalDialogHostObserver> observer_list_;
98
99  DISALLOW_COPY_AND_ASSIGN(WebContentsModalDialogHostViews);
100};
101
102// static
103const int BrowserViewLayout::kToolbarTabStripVerticalOverlap = 3;
104
105////////////////////////////////////////////////////////////////////////////////
106// BrowserViewLayout, public:
107
108BrowserViewLayout::BrowserViewLayout()
109    : browser_(NULL),
110      browser_view_(NULL),
111      bookmark_bar_(NULL),
112      infobar_container_(NULL),
113      contents_split_(NULL),
114      contents_container_(NULL),
115      download_shelf_(NULL),
116      dialog_host_(new WebContentsModalDialogHostViews(this)),
117      web_contents_modal_dialog_top_y_(-1) {
118}
119
120BrowserViewLayout::~BrowserViewLayout() {
121}
122
123void BrowserViewLayout::Init(Browser* browser,
124                             BrowserView* browser_view,
125                             InfoBarContainerView* infobar_container,
126                             views::SingleSplitView* contents_split,
127                             ContentsContainer* contents_container) {
128  browser_ = browser;
129  browser_view_ = browser_view;
130  infobar_container_ = infobar_container;
131  contents_split_ = contents_split;
132  contents_container_ = contents_container;
133}
134
135WebContentsModalDialogHost*
136    BrowserViewLayout::GetWebContentsModalDialogHost() {
137  return dialog_host_.get();
138}
139
140gfx::Size BrowserViewLayout::GetMinimumSize() {
141  gfx::Size tabstrip_size(
142      browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP) ?
143      browser_view_->tabstrip_->GetMinimumSize() : gfx::Size());
144  BrowserNonClientFrameView::TabStripInsets tab_strip_insets(
145      browser_view_->frame()->GetTabStripInsets(false));
146  gfx::Size toolbar_size(
147      (browser()->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) ||
148       browser()->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) ?
149           browser_view_->toolbar_->GetMinimumSize() : gfx::Size());
150  if (tabstrip_size.height() && toolbar_size.height())
151    toolbar_size.Enlarge(0, -kToolbarTabStripVerticalOverlap);
152  gfx::Size bookmark_bar_size;
153  if (bookmark_bar_ &&
154      bookmark_bar_->visible() &&
155      browser()->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR)) {
156    bookmark_bar_size = bookmark_bar_->GetMinimumSize();
157    bookmark_bar_size.Enlarge(0,
158        -(views::NonClientFrameView::kClientEdgeThickness +
159            bookmark_bar_->GetToolbarOverlap(true)));
160  }
161  // TODO: Adjust the minimum height for the find bar.
162
163  gfx::Size contents_size(contents_split_->GetMinimumSize());
164
165  int min_height = tabstrip_size.height() + toolbar_size.height() +
166      bookmark_bar_size.height() + contents_size.height();
167  int widths[] = {
168        tabstrip_size.width() + tab_strip_insets.left + tab_strip_insets.right,
169        toolbar_size.width(),
170        bookmark_bar_size.width(),
171        contents_size.width() };
172  int min_width = *std::max_element(&widths[0], &widths[arraysize(widths)]);
173  return gfx::Size(min_width, min_height);
174}
175
176gfx::Rect BrowserViewLayout::GetFindBarBoundingBox() const {
177  // This function returns the area the Find Bar can be laid out within. This
178  // basically implies the "user-perceived content area" of the browser
179  // window excluding the vertical scrollbar. The "user-perceived content area"
180  // excludes the detached bookmark bar (in the New Tab case) and any infobars
181  // since they are not _visually_ connected to the Toolbar.
182
183  // First determine the bounding box of the content area in Widget
184  // coordinates.
185  gfx::Rect bounding_box = contents_container_->ConvertRectToWidget(
186      contents_container_->GetLocalBounds());
187
188  TopContainerView* top_container = browser_view_->top_container();
189  gfx::Rect top_container_bounds = top_container->ConvertRectToWidget(
190      top_container->GetLocalBounds());
191
192  // The find bar is positioned 1 pixel above the bottom of the top container so
193  // that it occludes the border between the content area and the top container
194  // and looks connected to the top container.
195  int find_bar_y = top_container_bounds.bottom() - 1;
196
197  // Grow the height of |bounding_box| by the height of any elements between
198  // the top container and |contents_container_| such as the detached bookmark
199  // bar and any infobars.
200  int height_delta = bounding_box.y() - find_bar_y;
201  bounding_box.set_y(find_bar_y);
202  bounding_box.set_height(std::max(0, bounding_box.height() + height_delta));
203
204  // Finally decrease the width of the bounding box by the width of
205  // the vertical scroll bar.
206  int scrollbar_width = gfx::scrollbar_size();
207  bounding_box.set_width(std::max(0, bounding_box.width() - scrollbar_width));
208  if (base::i18n::IsRTL())
209    bounding_box.set_x(bounding_box.x() + scrollbar_width);
210
211  return bounding_box;
212}
213
214bool BrowserViewLayout::IsPositionInWindowCaption(
215    const gfx::Point& point) {
216  TabStrip* tabstrip = browser_view_->tabstrip_;
217  // Tab strip may transiently have no parent between the RemoveChildView() and
218  // AddChildView() caused by reparenting during an immersive mode reveal.
219  // During this window report that the point didn't hit a tab.
220  if (!tabstrip->parent())
221    return true;
222  gfx::Point tabstrip_point(point);
223  views::View::ConvertPointToTarget(browser_view_, tabstrip, &tabstrip_point);
224  return tabstrip->IsPositionInWindowCaption(tabstrip_point);
225}
226
227int BrowserViewLayout::NonClientHitTest(
228    const gfx::Point& point) {
229  // Since the TabStrip only renders in some parts of the top of the window,
230  // the un-obscured area is considered to be part of the non-client caption
231  // area of the window. So we need to treat hit-tests in these regions as
232  // hit-tests of the titlebar.
233
234  views::View* parent = browser_view_->parent();
235
236  gfx::Point point_in_browser_view_coords(point);
237  views::View::ConvertPointToTarget(
238      parent, browser_view_, &point_in_browser_view_coords);
239  gfx::Point test_point(point);
240
241  // Determine if the TabStrip exists and is capable of being clicked on. We
242  // might be a popup window without a TabStrip.
243  if (browser_view_->IsTabStripVisible()) {
244    // See if the mouse pointer is within the bounds of the TabStrip.
245    if (ConvertedHitTest(parent, browser_view_->tabstrip_, &test_point)) {
246      if (browser_view_->tabstrip_->IsPositionInWindowCaption(test_point))
247        return HTCAPTION;
248      return HTCLIENT;
249    }
250
251    // The top few pixels of the TabStrip are a drop-shadow - as we're pretty
252    // starved of dragable area, let's give it to window dragging (this also
253    // makes sense visually).
254    if (!browser_view_->IsMaximized() &&
255        (point_in_browser_view_coords.y() <
256         (browser_view_->tabstrip_->y() + kTabShadowSize))) {
257      // We return HTNOWHERE as this is a signal to our containing
258      // NonClientView that it should figure out what the correct hit-test
259      // code is given the mouse position...
260      return HTNOWHERE;
261    }
262  }
263
264  // If the point's y coordinate is below the top of the toolbar and otherwise
265  // within the bounds of this view, the point is considered to be within the
266  // client area.
267  gfx::Rect bv_bounds = browser_view_->bounds();
268  bv_bounds.Offset(0, browser_view_->toolbar_->y());
269  bv_bounds.set_height(bv_bounds.height() - browser_view_->toolbar_->y());
270  if (bv_bounds.Contains(point))
271    return HTCLIENT;
272
273  // If the point's y coordinate is above the top of the toolbar, but not in
274  // the tabstrip (per previous checking in this function), then we consider it
275  // in the window caption (e.g. the area to the right of the tabstrip
276  // underneath the window controls). However, note that we DO NOT return
277  // HTCAPTION here, because when the window is maximized the window controls
278  // will fall into this space (since the BrowserView is sized to entire size
279  // of the window at that point), and the HTCAPTION value will cause the
280  // window controls not to work. So we return HTNOWHERE so that the caller
281  // will hit-test the window controls before finally falling back to
282  // HTCAPTION.
283  bv_bounds = browser_view_->bounds();
284  bv_bounds.set_height(browser_view_->toolbar_->y());
285  if (bv_bounds.Contains(point))
286    return HTNOWHERE;
287
288  // If the point is somewhere else, delegate to the default implementation.
289  return browser_view_->views::ClientView::NonClientHitTest(point);
290}
291
292//////////////////////////////////////////////////////////////////////////////
293// BrowserViewLayout, views::LayoutManager implementation:
294
295void BrowserViewLayout::Layout(views::View* host) {
296  vertical_layout_rect_ = browser_view_->GetLocalBounds();
297  int top = LayoutTabStripRegion();
298  if (browser_view_->IsTabStripVisible()) {
299    int x = browser_view_->tabstrip_->GetMirroredX() +
300        browser_view_->GetMirroredX() +
301        browser_view_->frame()->GetThemeBackgroundXInset();
302    browser_view_->tabstrip_->SetBackgroundOffset(gfx::Point(x,
303        browser_view_->frame()->GetTabStripInsets(false).top));
304  }
305  top = LayoutToolbar(top);
306  top = LayoutBookmarkAndInfoBars(top);
307
308  // Top container requires updated toolbar and bookmark bar to compute size.
309  browser_view_->top_container_->SetSize(
310      browser_view_->top_container_->GetPreferredSize());
311
312  int bottom = LayoutDownloadShelf(browser_view_->height());
313  // Treat a detached bookmark bar as if the web contents container is shifted
314  // upwards and overlaps it.
315  top -= GetContentsOffsetForBookmarkBar();
316  LayoutContentsSplitView(top, bottom);
317
318  // Instant extended can put suggestions in a web view, which can require an
319  // offset to align with the omnibox. This offset must be recomputed after
320  // split view layout to account for infobar heights.
321  int active_top_margin = GetTopMarginForActiveContent();
322  bool needs_layout =
323      contents_container_->SetActiveTopMargin(active_top_margin);
324  needs_layout |=
325      contents_container_->SetOverlayTopMargin(GetTopMarginForOverlayContent());
326  if (needs_layout)
327    contents_container_->Layout();
328
329  // This must be done _after_ we lay out the WebContents since this
330  // code calls back into us to find the bounding box the find bar
331  // must be laid out within, and that code depends on the
332  // TabContentsContainer's bounds being up to date.
333  if (browser()->HasFindBarController()) {
334    browser()->GetFindBarController()->find_bar()->MoveWindowIfNecessary(
335        gfx::Rect(), true);
336  }
337
338  // Adjust any web contents modal dialogs.
339  dialog_host_->NotifyPositionRequiresUpdate();
340}
341
342// Return the preferred size which is the size required to give each
343// children their respective preferred size.
344gfx::Size BrowserViewLayout::GetPreferredSize(views::View* host) {
345  return gfx::Size();
346}
347
348//////////////////////////////////////////////////////////////////////////////
349// BrowserViewLayout, private:
350
351int BrowserViewLayout::LayoutTabStripRegion() {
352  TabStrip* tabstrip = browser_view_->tabstrip_;
353  if (!browser_view_->IsTabStripVisible()) {
354    tabstrip->SetVisible(false);
355    tabstrip->SetBounds(0, 0, 0, 0);
356    return 0;
357  }
358  // This retrieves the bounds for the tab strip based on whether or not we show
359  // anything to the left of it, like the incognito avatar.
360  gfx::Rect tabstrip_bounds(
361      browser_view_->frame()->GetBoundsForTabStrip(tabstrip));
362  gfx::Point tabstrip_origin(tabstrip_bounds.origin());
363  views::View::ConvertPointToTarget(browser_view_->parent(), browser_view_,
364                                    &tabstrip_origin);
365  tabstrip_bounds.set_origin(tabstrip_origin);
366
367  tabstrip->SetVisible(true);
368  tabstrip->SetBoundsRect(tabstrip_bounds);
369  int bottom = tabstrip_bounds.bottom();
370
371  // The metro window switcher sits at the far right edge of the tabstrip
372  // a |kWindowSwitcherOffsetX| pixels from the right edge.
373  // Only visible if there is more than one type of window to switch between.
374  // TODO(mad): update this code when more window types than just incognito
375  // and regular are available.
376  views::Button* switcher_button = browser_view_->window_switcher_button_;
377  if (switcher_button) {
378    if (browser()->profile()->HasOffTheRecordProfile() &&
379        chrome::FindBrowserWithProfile(
380            browser()->profile()->GetOriginalProfile(),
381            browser()->host_desktop_type()) != NULL) {
382      switcher_button->SetVisible(true);
383      int width = browser_view_->width();
384      gfx::Size ps = switcher_button->GetPreferredSize();
385      if (width > ps.width()) {
386        switcher_button->SetBounds(width - ps.width() - kWindowSwitcherOffsetX,
387                                   0,
388                                   ps.width(),
389                                   ps.height());
390      }
391    } else {
392      // We hide the button if the incognito profile is not alive.
393      // Note that Layout() is not called to all browser windows automatically
394      // when a profile goes away but we rely in the metro_driver.dll to call
395      // ::SetWindowPos( , .. SWP_SHOWWINDOW) which causes this function to
396      // be called again. This works both in showing or hidding the button.
397      switcher_button->SetVisible(false);
398    }
399  }
400
401  return bottom;
402}
403
404int BrowserViewLayout::LayoutToolbar(int top) {
405  ToolbarView* toolbar = browser_view_->toolbar_;
406  int browser_view_width = vertical_layout_rect_.width();
407  bool toolbar_visible = browser_view_->IsToolbarVisible();
408  toolbar->location_bar()->SetLocationEntryFocusable(toolbar_visible);
409  int y = top;
410  y -= (toolbar_visible && browser_view_->IsTabStripVisible()) ?
411        kToolbarTabStripVerticalOverlap : 0;
412  int height = toolbar_visible ? toolbar->GetPreferredSize().height() : 0;
413  toolbar->SetVisible(toolbar_visible);
414  toolbar->SetBounds(vertical_layout_rect_.x(), y, browser_view_width, height);
415
416  return y + height;
417}
418
419int BrowserViewLayout::LayoutBookmarkAndInfoBars(int top) {
420  web_contents_modal_dialog_top_y_ =
421      top + browser_view_->y() - kConstrainedWindowOverlap;
422  if (bookmark_bar_) {
423    // If we're showing the Bookmark bar in detached style, then we
424    // need to show any Info bar _above_ the Bookmark bar, since the
425    // Bookmark bar is styled to look like it's part of the page.
426    if (bookmark_bar_->IsDetached())
427      return LayoutBookmarkBar(LayoutInfoBar(top));
428    // Otherwise, Bookmark bar first, Info bar second.
429    top = std::max(browser_view_->toolbar_->bounds().bottom(),
430                   LayoutBookmarkBar(top));
431  }
432  return LayoutInfoBar(top);
433}
434
435int BrowserViewLayout::LayoutBookmarkBar(int top) {
436  int y = top;
437  if (!browser_view_->IsBookmarkBarVisible()) {
438    bookmark_bar_->SetVisible(false);
439    // TODO(jamescook): Don't change the bookmark bar height when it is
440    // invisible, so we can use its height for layout even in that state.
441    bookmark_bar_->SetBounds(0, y, browser_view_->width(), 0);
442    return y;
443  }
444
445  bookmark_bar_->set_infobar_visible(InfobarVisible());
446  int bookmark_bar_height = bookmark_bar_->GetPreferredSize().height();
447  y -= views::NonClientFrameView::kClientEdgeThickness +
448      bookmark_bar_->GetToolbarOverlap(false);
449  bookmark_bar_->SetVisible(true);
450  bookmark_bar_->SetBounds(vertical_layout_rect_.x(), y,
451                                 vertical_layout_rect_.width(),
452                                 bookmark_bar_height);
453  return y + bookmark_bar_height;
454}
455
456int BrowserViewLayout::LayoutInfoBar(int top) {
457  // In immersive fullscreen, the infobar always starts near the top of the
458  // screen, just under the "light bar" rectangular stripes.
459  if (browser_view_->immersive_mode_controller_->IsEnabled()) {
460    top = browser_view_->immersive_mode_controller_->ShouldHideTabIndicators() ?
461        browser_view_->y() :
462        browser_view_->y() + TabStrip::GetImmersiveHeight();
463  }
464  InfoBarContainerView* infobar_container = browser_view_->infobar_container_;
465  // Raise the |infobar_container| by its vertical overlap.
466  infobar_container->SetVisible(InfobarVisible());
467  int height;
468  int overlapped_top = top - infobar_container->GetVerticalOverlap(&height);
469  infobar_container->SetBounds(vertical_layout_rect_.x(),
470                               overlapped_top,
471                               vertical_layout_rect_.width(),
472                               height);
473  return overlapped_top + height;
474}
475
476void BrowserViewLayout::LayoutContentsSplitView(int top, int bottom) {
477  // |contents_split_| contains web page contents and devtools.
478  // See browser_view.h for details.
479  gfx::Rect contents_split_bounds(vertical_layout_rect_.x(),
480                                  top,
481                                  vertical_layout_rect_.width(),
482                                  std::max(0, bottom - top));
483  contents_split_->SetBoundsRect(contents_split_bounds);
484}
485
486int BrowserViewLayout::GetContentsOffsetForBookmarkBar() {
487  // If the bookmark bar is hidden or attached to the omnibox the web contents
488  // will appear directly underneath it and does not need an offset.
489  if (!bookmark_bar_ ||
490      !browser_view_->IsBookmarkBarVisible() ||
491      !bookmark_bar_->IsDetached()) {
492    return 0;
493  }
494
495  // Dev tools.
496  if (contents_split_->child_at(1) && contents_split_->child_at(1)->visible())
497    return 0;
498
499  // Offset for the detached bookmark bar.
500  return bookmark_bar_->height() -
501      views::NonClientFrameView::kClientEdgeThickness;
502}
503
504int BrowserViewLayout::GetTopMarginForActiveContent() {
505  // During an immersive reveal, if instant extended is showing suggestions
506  // in the main active web view, ensure that active web view appears aligned
507  // with the bottom of the omnibox.
508  InstantUIState instant_ui_state = GetInstantUIState();
509  if (instant_ui_state == kInstantUIFullPageResults &&
510      browser_view_->immersive_mode_controller()->IsRevealed())
511    return GetTopMarginForImmersiveInstant();
512
513  // Usually we only use a margin if there's a detached bookmarks bar.
514  return GetContentsOffsetForBookmarkBar();
515}
516
517int BrowserViewLayout::GetTopMarginForOverlayContent() {
518  // During an immersive reveal, if instant extended is showing suggestions
519  // in an overlay web view, ensure that overlay web view appears aligned
520  // with the bottom of the omnibox.
521  InstantUIState instant_ui_state = GetInstantUIState();
522  if (instant_ui_state == kInstantUIOverlay &&
523      browser_view_->immersive_mode_controller()->IsRevealed())
524    return GetTopMarginForImmersiveInstant();
525
526  // Usually the overlay content is aligned with the active web content.
527  return 0;
528}
529
530int BrowserViewLayout::GetTopMarginForImmersiveInstant() {
531  // Compute the position of the bottom edge of the top container views,
532  // expressed as an offset in the coordinates of |contents_container_|,
533  // because the offset will be applied in |contents_container_| layout.
534  // NOTE: This requires contents_split_ layout to be complete, as the
535  // coordinate system conversion depends on the contents_split_ origin.
536  gfx::Point bottom_edge(0, browser_view_->top_container_->height());
537  views::View::ConvertPointToTarget(browser_view_->top_container_,
538                                    contents_container_,
539                                    &bottom_edge);
540  return bottom_edge.y();
541}
542
543BrowserViewLayout::InstantUIState BrowserViewLayout::GetInstantUIState() {
544  if (!browser()->search_model()->mode().is_search())
545    return kInstantUINone;
546
547  // If the search suggestions are already being displayed in the overlay
548  // contents then return kInstantUIOverlay.
549  if (contents_container_->overlay_height() > 0)
550    return kInstantUIOverlay;
551
552  // Top bars stay visible until the results page notifies Chrome it is ready.
553  if (browser()->search_model()->top_bars_visible())
554    return kInstantUINone;
555
556  return kInstantUIFullPageResults;
557}
558
559int BrowserViewLayout::LayoutDownloadShelf(int bottom) {
560  // Re-layout the shelf either if it is visible or if its close animation
561  // is currently running.
562  if (browser_view_->IsDownloadShelfVisible() ||
563      (download_shelf_ && download_shelf_->IsClosing())) {
564    bool visible = browser()->SupportsWindowFeature(
565        Browser::FEATURE_DOWNLOADSHELF);
566    DCHECK(download_shelf_);
567    int height = visible ? download_shelf_->GetPreferredSize().height() : 0;
568    download_shelf_->SetVisible(visible);
569    download_shelf_->SetBounds(vertical_layout_rect_.x(), bottom - height,
570                               vertical_layout_rect_.width(), height);
571    download_shelf_->Layout();
572    bottom -= height;
573  }
574  return bottom;
575}
576
577bool BrowserViewLayout::InfobarVisible() const {
578  // Cast to a views::View to access GetPreferredSize().
579  views::View* infobar_container = infobar_container_;
580  // NOTE: Can't check if the size IsEmpty() since it's always 0-width.
581  return browser_->SupportsWindowFeature(Browser::FEATURE_INFOBAR) &&
582      (infobar_container->GetPreferredSize().height() != 0);
583}
584