browser_view_layout.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 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 "chrome/browser/ui/browser_finder.h"
8#include "chrome/browser/ui/find_bar/find_bar.h"
9#include "chrome/browser/ui/find_bar/find_bar_controller.h"
10#include "chrome/browser/ui/search/search_model.h"
11#include "chrome/browser/ui/search/search_ui.h"
12#include "chrome/browser/ui/view_ids.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/infobars/infobar_container_view.h"
19#include "chrome/browser/ui/views/tabs/tab_strip.h"
20#include "chrome/browser/ui/views/toolbar_view.h"
21#include "ui/base/hit_test.h"
22#include "ui/gfx/point.h"
23#include "ui/gfx/scrollbar_size.h"
24#include "ui/gfx/size.h"
25#include "ui/views/controls/single_split_view.h"
26#include "ui/views/controls/webview/webview.h"
27
28namespace {
29
30// The visible height of the shadow above the tabs. Clicks in this area are
31// treated as clicks to the frame, rather than clicks to the tab.
32const int kTabShadowSize = 2;
33// The vertical overlap between the TabStrip and the Toolbar.
34const int kToolbarTabStripVerticalOverlap = 3;
35// The number of pixels the bookmark bar should overlap the spacer by if the
36// spacer is visible.
37const int kSpacerBookmarkBarOverlap = 1;
38// The number of pixels the metro switcher is offset from the right edge.
39const int kWindowSwitcherOffsetX = 7;
40// The number of pixels the constrained window should overlap the bottom
41// of the omnibox.
42const int kConstrainedWindowOverlap = 3;
43
44// Combines View::ConvertPointToTarget and View::HitTest for a given |point|.
45// Converts |point| from |src| to |dst| and hit tests it against |dst|. The
46// converted |point| can then be retrieved and used for additional tests.
47bool ConvertedHitTest(views::View* src, views::View* dst, gfx::Point* point) {
48  DCHECK(src);
49  DCHECK(dst);
50  DCHECK(point);
51  views::View::ConvertPointToTarget(src, dst, point);
52  return dst->HitTestPoint(*point);
53}
54
55}  // namespace
56
57////////////////////////////////////////////////////////////////////////////////
58// BrowserViewLayout, public:
59
60BrowserViewLayout::BrowserViewLayout()
61    : tabstrip_(NULL),
62      toolbar_(NULL),
63      contents_split_(NULL),
64      contents_container_(NULL),
65      infobar_container_(NULL),
66      download_shelf_(NULL),
67      active_bookmark_bar_(NULL),
68      browser_view_(NULL),
69      find_bar_y_(0) {
70}
71
72BrowserViewLayout::~BrowserViewLayout() {
73}
74
75bool BrowserViewLayout::GetConstrainedWindowTopY(int* top_y) {
76  DCHECK(top_y);
77  *top_y = constrained_window_top_y;
78  return (constrained_window_top_y >= 0);
79}
80
81gfx::Size BrowserViewLayout::GetMinimumSize() {
82  gfx::Size tabstrip_size(
83      browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP) ?
84      tabstrip_->GetMinimumSize() : gfx::Size());
85  BrowserNonClientFrameView::TabStripInsets tab_strip_insets(
86      browser_view_->frame()->GetTabStripInsets(false));
87  gfx::Size toolbar_size(
88      (browser()->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) ||
89       browser()->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) ?
90      toolbar_->GetMinimumSize() : gfx::Size());
91  if (tabstrip_size.height() && toolbar_size.height())
92    toolbar_size.Enlarge(0, -kToolbarTabStripVerticalOverlap);
93  gfx::Size bookmark_bar_size;
94  if (active_bookmark_bar_ &&
95      browser()->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR)) {
96    bookmark_bar_size = active_bookmark_bar_->GetMinimumSize();
97    bookmark_bar_size.Enlarge(0,
98        -(views::NonClientFrameView::kClientEdgeThickness +
99            active_bookmark_bar_->GetToolbarOverlap(true)));
100  }
101  gfx::Size contents_size(contents_split_->GetMinimumSize());
102
103  int min_height = tabstrip_size.height() + toolbar_size.height() +
104      bookmark_bar_size.height() + contents_size.height();
105  int widths[] = {
106        tabstrip_size.width() + tab_strip_insets.left + tab_strip_insets.right,
107        toolbar_size.width(),
108        bookmark_bar_size.width(),
109        contents_size.width() };
110  int min_width = *std::max_element(&widths[0], &widths[arraysize(widths)]);
111  return gfx::Size(min_width, min_height);
112}
113
114gfx::Rect BrowserViewLayout::GetFindBarBoundingBox() const {
115  // This function returns the area the Find Bar can be laid out
116  // within. This basically implies the "user-perceived content
117  // area" of the browser window excluding the vertical
118  // scrollbar. This is not quite so straightforward as positioning
119  // based on the TabContentsContainer since the BookmarkBarView may
120  // be visible but not persistent (in the New Tab case) and we
121  // position the Find Bar over the top of it in that case since the
122  // BookmarkBarView is not _visually_ connected to the Toolbar.
123
124  // First determine the bounding box of the content area in Widget
125  // coordinates.
126  gfx::Rect bounding_box = contents_container_->ConvertRectToWidget(
127      contents_container_->GetLocalBounds());
128
129  // Adjust the position and size of the bounding box by the find bar offset
130  // calculated during the last Layout.
131  int height_delta = find_bar_y_ - bounding_box.y();
132  bounding_box.set_y(find_bar_y_);
133  bounding_box.set_height(std::max(0, bounding_box.height() + height_delta));
134
135  // Finally decrease the width of the bounding box by the width of
136  // the vertical scroll bar.
137  int scrollbar_width = gfx::scrollbar_size();
138  bounding_box.set_width(std::max(0, bounding_box.width() - scrollbar_width));
139  if (base::i18n::IsRTL())
140    bounding_box.set_x(bounding_box.x() + scrollbar_width);
141
142  return bounding_box;
143}
144
145bool BrowserViewLayout::IsPositionInWindowCaption(
146    const gfx::Point& point) {
147  gfx::Point tabstrip_point(point);
148  views::View::ConvertPointToTarget(browser_view_, tabstrip_, &tabstrip_point);
149  return tabstrip_->IsPositionInWindowCaption(tabstrip_point);
150}
151
152int BrowserViewLayout::NonClientHitTest(
153    const gfx::Point& point) {
154  // Since the TabStrip only renders in some parts of the top of the window,
155  // the un-obscured area is considered to be part of the non-client caption
156  // area of the window. So we need to treat hit-tests in these regions as
157  // hit-tests of the titlebar.
158
159  views::View* parent = browser_view_->parent();
160
161  gfx::Point point_in_browser_view_coords(point);
162  views::View::ConvertPointToTarget(
163      parent, browser_view_, &point_in_browser_view_coords);
164  gfx::Point test_point(point);
165
166  // Determine if the TabStrip exists and is capable of being clicked on. We
167  // might be a popup window without a TabStrip.
168  if (browser_view_->IsTabStripVisible()) {
169    // See if the mouse pointer is within the bounds of the TabStrip.
170    if (ConvertedHitTest(parent, tabstrip_, &test_point)) {
171      if (tabstrip_->IsPositionInWindowCaption(test_point))
172        return HTCAPTION;
173      return HTCLIENT;
174    }
175
176    // The top few pixels of the TabStrip are a drop-shadow - as we're pretty
177    // starved of dragable area, let's give it to window dragging (this also
178    // makes sense visually).
179    if (!browser_view_->IsMaximized() &&
180        (point_in_browser_view_coords.y() <
181         (tabstrip_->y() + kTabShadowSize))) {
182      // We return HTNOWHERE as this is a signal to our containing
183      // NonClientView that it should figure out what the correct hit-test
184      // code is given the mouse position...
185      return HTNOWHERE;
186    }
187  }
188
189  // If the point's y coordinate is below the top of the toolbar and otherwise
190  // within the bounds of this view, the point is considered to be within the
191  // client area.
192  gfx::Rect bv_bounds = browser_view_->bounds();
193  bv_bounds.Offset(0, toolbar_->y());
194  bv_bounds.set_height(bv_bounds.height() - toolbar_->y());
195  if (bv_bounds.Contains(point))
196    return HTCLIENT;
197
198  // If the point's y coordinate is above the top of the toolbar, but not in
199  // the tabstrip (per previous checking in this function), then we consider it
200  // in the window caption (e.g. the area to the right of the tabstrip
201  // underneath the window controls). However, note that we DO NOT return
202  // HTCAPTION here, because when the window is maximized the window controls
203  // will fall into this space (since the BrowserView is sized to entire size
204  // of the window at that point), and the HTCAPTION value will cause the
205  // window controls not to work. So we return HTNOWHERE so that the caller
206  // will hit-test the window controls before finally falling back to
207  // HTCAPTION.
208  bv_bounds = browser_view_->bounds();
209  bv_bounds.set_height(toolbar_->y());
210  if (bv_bounds.Contains(point))
211    return HTNOWHERE;
212
213  // If the point is somewhere else, delegate to the default implementation.
214  return browser_view_->views::ClientView::NonClientHitTest(point);
215}
216
217//////////////////////////////////////////////////////////////////////////////
218// BrowserViewLayout, views::LayoutManager implementation:
219
220void BrowserViewLayout::Installed(views::View* host) {
221  toolbar_ = NULL;
222  contents_split_ = NULL;
223  contents_container_ = NULL;
224  infobar_container_ = NULL;
225  download_shelf_ = NULL;
226  active_bookmark_bar_ = NULL;
227  tabstrip_ = NULL;
228  browser_view_ = static_cast<BrowserView*>(host);
229}
230
231void BrowserViewLayout::Uninstalled(views::View* host) {}
232
233void BrowserViewLayout::ViewAdded(views::View* host, views::View* view) {
234  switch (view->id()) {
235    case VIEW_ID_CONTENTS_SPLIT: {
236      contents_split_ = static_cast<views::SingleSplitView*>(view);
237      // We're installed as the LayoutManager before BrowserView creates the
238      // contents, so we have to set contents_container_ here rather than in
239      // Installed.
240      contents_container_ = browser_view_->contents_;
241      break;
242    }
243    case VIEW_ID_INFO_BAR_CONTAINER:
244      infobar_container_ = view;
245      break;
246    case VIEW_ID_DOWNLOAD_SHELF:
247      download_shelf_ = static_cast<DownloadShelfView*>(view);
248      break;
249    case VIEW_ID_BOOKMARK_BAR:
250      active_bookmark_bar_ = static_cast<BookmarkBarView*>(view);
251      break;
252    case VIEW_ID_TOOLBAR:
253      toolbar_ = static_cast<ToolbarView*>(view);
254      break;
255    case VIEW_ID_TAB_STRIP:
256      tabstrip_ = static_cast<TabStrip*>(view);
257      break;
258  }
259}
260
261void BrowserViewLayout::ViewRemoved(views::View* host, views::View* view) {
262  switch (view->id()) {
263    case VIEW_ID_BOOKMARK_BAR:
264      active_bookmark_bar_ = NULL;
265      break;
266  }
267}
268
269void BrowserViewLayout::Layout(views::View* host) {
270  vertical_layout_rect_ = browser_view_->GetLocalBounds();
271  int top = LayoutTabStripRegion();
272  if (browser_view_->IsTabStripVisible()) {
273    int x = tabstrip_->GetMirroredX() + browser_view_->GetMirroredX() +
274        browser_view_->frame()->GetThemeBackgroundXInset();
275    tabstrip_->SetBackgroundOffset(gfx::Point(x,
276        browser_view_->frame()->GetTabStripInsets(false).top));
277  }
278  top = LayoutToolbar(top);
279  top = LayoutBookmarkAndInfoBars(top);
280  int bottom = LayoutDownloadShelf(browser_view_->height());
281  int active_top_margin = GetTopMarginForActiveContent();
282  top -= active_top_margin;
283  contents_container_->SetActiveTopMargin(active_top_margin);
284  LayoutTabContents(top, bottom);
285  // This must be done _after_ we lay out the WebContents since this
286  // code calls back into us to find the bounding box the find bar
287  // must be laid out within, and that code depends on the
288  // TabContentsContainer's bounds being up to date.
289  if (browser()->HasFindBarController()) {
290    browser()->GetFindBarController()->find_bar()->MoveWindowIfNecessary(
291        gfx::Rect(), true);
292  }
293
294  if (active_bookmark_bar_ && browser()->search_model()->mode().is_ntp())
295    LayoutBookmarkBarAtBottom();
296}
297
298// Return the preferred size which is the size required to give each
299// children their respective preferred size.
300gfx::Size BrowserViewLayout::GetPreferredSize(views::View* host) {
301  return gfx::Size();
302}
303
304//////////////////////////////////////////////////////////////////////////////
305// BrowserViewLayout, private:
306
307Browser* BrowserViewLayout::browser() {
308  return browser_view_->browser();
309}
310
311const Browser* BrowserViewLayout::browser() const {
312  return browser_view_->browser();
313}
314
315int BrowserViewLayout::LayoutTabStripRegion() {
316  if (!browser_view_->IsTabStripVisible()) {
317    tabstrip_->SetVisible(false);
318    tabstrip_->SetBounds(0, 0, 0, 0);
319    return 0;
320  }
321  // This retrieves the bounds for the tab strip based on whether or not we show
322  // anything to the left of it, like the incognito avatar.
323  gfx::Rect tabstrip_bounds(
324      browser_view_->frame()->GetBoundsForTabStrip(tabstrip_));
325  gfx::Point tabstrip_origin(tabstrip_bounds.origin());
326  views::View::ConvertPointToTarget(browser_view_->parent(), browser_view_,
327                                  &tabstrip_origin);
328  tabstrip_bounds.set_origin(tabstrip_origin);
329
330  tabstrip_->SetVisible(true);
331  tabstrip_->SetBoundsRect(tabstrip_bounds);
332  int bottom = tabstrip_bounds.bottom();
333
334  // The metro window switcher sits at the far right edge of the tabstrip
335  // a |kWindowSwitcherOffsetX| pixels from the right edge.
336  // Only visible if there is more than one type of window to switch between.
337  // TODO(mad): update this code when more window types than just incognito
338  // and regular are available.
339  views::Button* switcher_button = browser_view_->window_switcher_button_;
340  if (switcher_button) {
341    if (browser()->profile()->HasOffTheRecordProfile() &&
342        browser::FindBrowserWithProfile(
343            browser()->profile()->GetOriginalProfile(),
344            browser()->host_desktop_type()) != NULL) {
345      switcher_button->SetVisible(true);
346      int width = browser_view_->width();
347      gfx::Size ps = switcher_button->GetPreferredSize();
348      if (width > ps.width()) {
349        switcher_button->SetBounds(width - ps.width() - kWindowSwitcherOffsetX,
350                                   0,
351                                   ps.width(),
352                                   ps.height());
353      }
354    } else {
355      // We hide the button if the incognito profile is not alive.
356      // Note that Layout() is not called to all browser windows automatically
357      // when a profile goes away but we rely in the metro_driver.dll to call
358      // ::SetWindowPos( , .. SWP_SHOWWINDOW) which causes this function to
359      // be called again. This works both in showing or hidding the button.
360      switcher_button->SetVisible(false);
361    }
362  }
363
364  return bottom;
365}
366
367int BrowserViewLayout::LayoutToolbar(int top) {
368  int browser_view_width = vertical_layout_rect_.width();
369  bool toolbar_visible = browser_view_->IsToolbarVisible();
370  toolbar_->location_bar()->SetLocationEntryFocusable(toolbar_visible);
371  int y = top;
372  y -= (toolbar_visible && browser_view_->IsTabStripVisible()) ?
373        kToolbarTabStripVerticalOverlap : 0;
374  int height = toolbar_visible ? toolbar_->GetPreferredSize().height() : 0;
375  toolbar_->SetVisible(toolbar_visible);
376  toolbar_->SetBounds(vertical_layout_rect_.x(), y, browser_view_width, height);
377
378  return y + height;
379}
380
381int BrowserViewLayout::LayoutBookmarkAndInfoBars(int top) {
382  constrained_window_top_y =
383      top + browser_view_->y() - kConstrainedWindowOverlap;
384  find_bar_y_ = top + browser_view_->y() - 1;
385  if (active_bookmark_bar_) {
386    // If the bookmark bar is showing in detached style:
387    // - for non-NTP mode, show any Info bar _above_ the bookmark bar, since the
388    //   bookmark bar is styled to look like it's part of the page.
389    // - otherwise, show the bookmark bar at the bottom of content view, so just
390    //   lay out infobar here; bottom bookmark bar is laid out in
391    //   |SearchNTPContainerView::Layout| where content view is also laid out.
392    if (active_bookmark_bar_->IsDetached()) {
393      int infobar_top = LayoutInfoBar(top);
394      return browser_view_->browser()->search_model()->mode().is_ntp() ?
395          infobar_top : LayoutBookmarkBarAtTop(infobar_top);
396    }
397    // Otherwise, Bookmark bar first, Info bar second.
398    top = std::max(toolbar_->bounds().bottom(), LayoutBookmarkBarAtTop(top));
399  }
400  find_bar_y_ = top + browser_view_->y() - 1;
401  return LayoutInfoBar(top);
402}
403
404int BrowserViewLayout::LayoutBookmarkBarAtTop(int top) {
405  DCHECK(active_bookmark_bar_);
406  int y = top;
407  if (!browser_view_->IsBookmarkBarVisible()) {
408    active_bookmark_bar_->SetVisible(false);
409    active_bookmark_bar_->SetBounds(0, y, browser_view_->width(), 0);
410    return y;
411  }
412
413  active_bookmark_bar_->set_infobar_visible(InfobarVisible());
414  int bookmark_bar_height = active_bookmark_bar_->GetPreferredSize().height();
415  y -= views::NonClientFrameView::kClientEdgeThickness +
416      active_bookmark_bar_->GetToolbarOverlap(false);
417  active_bookmark_bar_->SetVisible(true);
418  active_bookmark_bar_->SetBounds(vertical_layout_rect_.x(), y,
419                                  vertical_layout_rect_.width(),
420                                  bookmark_bar_height);
421  return y + bookmark_bar_height;
422}
423
424void BrowserViewLayout::LayoutBookmarkBarAtBottom() {
425  DCHECK(active_bookmark_bar_);
426  // Layout bookmark bar at bottom of content view in the y-direction.
427  // Bookmark bar is child of |BrowserView| while content view is child of
428  // ContentsContainer, so convert its bottom coordinate relative to
429  // |BrowserView|.
430  gfx::Point content_bottom(
431      0, browser_view_->contents_container_->bounds().bottom());
432  views::View::ConvertPointToTarget(
433      browser_view_->contents_container_->parent(), browser_view_,
434      &content_bottom);
435  // Only show bookmark bar if height of content view is >=
436  // chrome::search::kMinContentHeightForBottomBookmarkBar.
437  if (browser_view_->contents_container_->height() <
438      chrome::search::kMinContentHeightForBottomBookmarkBar ||
439      !browser_view_->IsBookmarkBarVisible()) {
440    active_bookmark_bar_->SetVisible(false);
441    active_bookmark_bar_->SetBounds(0, content_bottom.y(),
442                                    browser_view_->width(), 0);
443    return;
444  }
445
446  // BookmarkBarView uses infobar visibility to determine toolbar overlap, which
447  // is 0 if bookmark bar is detached and infobar is visible.  Since the
448  // bookmark bar on the NTP is detached at bottom of content view, toolbar
449  // overlap is irrelevant.  So set infobar visible to force no toolbar overlap.
450  active_bookmark_bar_->set_infobar_visible(true);
451  active_bookmark_bar_->SetVisible(true);
452
453  // Horizontally center bookmark bar.
454  const int kMaxNtpBookmarkBarWidth = 720;
455  const int kNtpBookmarkBarWidthPadding = 130;
456  int width = vertical_layout_rect_.width() - 2 * kNtpBookmarkBarWidthPadding;
457  if (width > kMaxNtpBookmarkBarWidth)
458    width = kMaxNtpBookmarkBarWidth;
459  int x_pos = (vertical_layout_rect_.width() - width) / 2;
460  int height = active_bookmark_bar_->GetPreferredSize().height();
461  active_bookmark_bar_->SetBounds(x_pos, content_bottom.y() - height,
462                                  width, height);
463}
464
465int BrowserViewLayout::LayoutInfoBar(int top) {
466  // Raise the |infobar_container_| by its vertical overlap.
467  infobar_container_->SetVisible(InfobarVisible());
468  int height;
469  int overlapped_top = top -
470      static_cast<InfoBarContainerView*>(infobar_container_)->
471          GetVerticalOverlap(&height);
472  infobar_container_->SetBounds(vertical_layout_rect_.x(),
473                                overlapped_top,
474                                vertical_layout_rect_.width(),
475                                height);
476  return overlapped_top + height;
477}
478
479void BrowserViewLayout::LayoutTabContents(int top, int bottom) {
480  // The ultimate idea is to calculate bounds and reserved areas for all
481  // contents views first and then resize them all, so every view
482  // (and its contents) is resized and laid out only once.
483
484  // The views hierarcy (see browser_view.h for more details):
485  // contents_split_ -> [contents_container_ | devtools]
486
487  gfx::Rect contents_bounds;
488  gfx::Rect devtools_bounds;
489
490  gfx::Rect contents_split_bounds(vertical_layout_rect_.x(), top,
491                                  vertical_layout_rect_.width(),
492                                  std::max(0, bottom - top));
493  gfx::Point contents_split_offset(
494      contents_split_bounds.x() - contents_split_->bounds().x(),
495      contents_split_bounds.y() - contents_split_->bounds().y());
496
497  // Layout resize corner and calculate reserved contents rects here as all
498  // contents view bounds are already determined, but not yet set at this point,
499  // so contents will be laid out once at most.
500  gfx::Rect browser_reserved_rect;
501  if (!browser_view_->frame_->IsMaximized() &&
502      !browser_view_->frame_->IsFullscreen()) {
503    gfx::Size resize_corner_size = browser_view_->GetResizeCornerSize();
504    if (!resize_corner_size.IsEmpty()) {
505      gfx::Rect bounds = browser_view_->GetContentsBounds();
506      gfx::Point resize_corner_origin(
507          bounds.right() - resize_corner_size.width(),
508          bounds.bottom() - resize_corner_size.height());
509      browser_reserved_rect =
510          gfx::Rect(resize_corner_origin, resize_corner_size);
511    }
512  }
513
514  // Now it's safe to actually resize all contents views in the hierarchy.
515  contents_split_->SetBoundsRect(contents_split_bounds);
516}
517
518int BrowserViewLayout::GetTopMarginForActiveContent() {
519  if (!active_bookmark_bar_ || !browser_view_->IsBookmarkBarVisible() ||
520      !active_bookmark_bar_->IsDetached() ||
521      // For |NTP| mode, bookmark bar does NOT overlap with top of content view;
522      // instead, it "overlaps" with bottom of content view, which is handled
523      // in |SearchNTPContainerView::Layout|.
524      browser()->search_model()->mode().is_ntp()) {
525    return 0;
526  }
527
528  if (contents_split_->child_at(1) && contents_split_->child_at(1)->visible())
529    return 0;
530
531  // Adjust for separator.
532  return active_bookmark_bar_->height() -
533      views::NonClientFrameView::kClientEdgeThickness;
534}
535
536int BrowserViewLayout::LayoutDownloadShelf(int bottom) {
537  // Re-layout the shelf either if it is visible or if its close animation
538  // is currently running.
539  if (browser_view_->IsDownloadShelfVisible() ||
540      (download_shelf_ && download_shelf_->IsClosing())) {
541    bool visible = browser()->SupportsWindowFeature(
542        Browser::FEATURE_DOWNLOADSHELF);
543    DCHECK(download_shelf_);
544    int height = visible ? download_shelf_->GetPreferredSize().height() : 0;
545    download_shelf_->SetVisible(visible);
546    download_shelf_->SetBounds(vertical_layout_rect_.x(), bottom - height,
547                               vertical_layout_rect_.width(), height);
548    download_shelf_->Layout();
549    bottom -= height;
550  }
551  return bottom;
552}
553
554bool BrowserViewLayout::InfobarVisible() const {
555  // NOTE: Can't check if the size IsEmpty() since it's always 0-width.
556  return browser()->SupportsWindowFeature(Browser::FEATURE_INFOBAR) &&
557      (infobar_container_->GetPreferredSize().height() != 0);
558}
559