browser_view_layout.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "chrome/browser/profiles/profile.h"
8#include "chrome/browser/ui/browser_finder.h"
9#include "chrome/browser/ui/find_bar/find_bar.h"
10#include "chrome/browser/ui/find_bar/find_bar_controller.h"
11#include "chrome/browser/ui/search/search_model.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/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 "ui/base/hit_test.h"
24#include "ui/gfx/point.h"
25#include "ui/gfx/scrollbar_size.h"
26#include "ui/gfx/size.h"
27#include "ui/views/controls/single_split_view.h"
28#include "ui/views/controls/webview/webview.h"
29
30namespace {
31
32// The visible height of the shadow above the tabs. Clicks in this area are
33// treated as clicks to the frame, rather than clicks to the tab.
34const int kTabShadowSize = 2;
35// The vertical overlap between the TabStrip and the Toolbar.
36const int kToolbarTabStripVerticalOverlap = 3;
37// The number of pixels the bookmark bar should overlap the spacer by if the
38// spacer is visible.
39const int kSpacerBookmarkBarOverlap = 1;
40// The number of pixels the metro switcher is offset from the right edge.
41const int kWindowSwitcherOffsetX = 7;
42// The number of pixels the constrained window should overlap the bottom
43// of the omnibox.
44const int kConstrainedWindowOverlap = 3;
45
46// Combines View::ConvertPointToTarget and View::HitTest for a given |point|.
47// Converts |point| from |src| to |dst| and hit tests it against |dst|. The
48// converted |point| can then be retrieved and used for additional tests.
49bool ConvertedHitTest(views::View* src, views::View* dst, gfx::Point* point) {
50  DCHECK(src);
51  DCHECK(dst);
52  DCHECK(point);
53  views::View::ConvertPointToTarget(src, dst, point);
54  return dst->HitTestPoint(*point);
55}
56
57}  // namespace
58
59////////////////////////////////////////////////////////////////////////////////
60// BrowserViewLayout, public:
61
62BrowserViewLayout::BrowserViewLayout()
63    : contents_split_(NULL),
64      contents_container_(NULL),
65      download_shelf_(NULL),
66      active_bookmark_bar_(NULL),
67      browser_view_(NULL),
68      find_bar_y_(0),
69      constrained_window_top_y_(-1) {
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      browser_view_->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           browser_view_->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  TabStrip* tabstrip = browser_view_->tabstrip_;
148  // Tab strip may transiently have no parent between the RemoveChildView() and
149  // AddChildView() caused by reparenting during an immersive mode reveal.
150  // During this window report that the point didn't hit a tab.
151  if (!tabstrip->parent())
152    return true;
153  gfx::Point tabstrip_point(point);
154  views::View::ConvertPointToTarget(browser_view_, tabstrip, &tabstrip_point);
155  return tabstrip->IsPositionInWindowCaption(tabstrip_point);
156}
157
158int BrowserViewLayout::NonClientHitTest(
159    const gfx::Point& point) {
160  // Since the TabStrip only renders in some parts of the top of the window,
161  // the un-obscured area is considered to be part of the non-client caption
162  // area of the window. So we need to treat hit-tests in these regions as
163  // hit-tests of the titlebar.
164
165  views::View* parent = browser_view_->parent();
166
167  gfx::Point point_in_browser_view_coords(point);
168  views::View::ConvertPointToTarget(
169      parent, browser_view_, &point_in_browser_view_coords);
170  gfx::Point test_point(point);
171
172  // Determine if the TabStrip exists and is capable of being clicked on. We
173  // might be a popup window without a TabStrip.
174  if (browser_view_->IsTabStripVisible()) {
175    // See if the mouse pointer is within the bounds of the TabStrip.
176    if (ConvertedHitTest(parent, browser_view_->tabstrip_, &test_point)) {
177      if (browser_view_->tabstrip_->IsPositionInWindowCaption(test_point))
178        return HTCAPTION;
179      return HTCLIENT;
180    }
181
182    // The top few pixels of the TabStrip are a drop-shadow - as we're pretty
183    // starved of dragable area, let's give it to window dragging (this also
184    // makes sense visually).
185    if (!browser_view_->IsMaximized() &&
186        (point_in_browser_view_coords.y() <
187         (browser_view_->tabstrip_->y() + kTabShadowSize))) {
188      // We return HTNOWHERE as this is a signal to our containing
189      // NonClientView that it should figure out what the correct hit-test
190      // code is given the mouse position...
191      return HTNOWHERE;
192    }
193  }
194
195  // If the point's y coordinate is below the top of the toolbar and otherwise
196  // within the bounds of this view, the point is considered to be within the
197  // client area.
198  gfx::Rect bv_bounds = browser_view_->bounds();
199  bv_bounds.Offset(0, browser_view_->toolbar_->y());
200  bv_bounds.set_height(bv_bounds.height() - browser_view_->toolbar_->y());
201  if (bv_bounds.Contains(point))
202    return HTCLIENT;
203
204  // If the point's y coordinate is above the top of the toolbar, but not in
205  // the tabstrip (per previous checking in this function), then we consider it
206  // in the window caption (e.g. the area to the right of the tabstrip
207  // underneath the window controls). However, note that we DO NOT return
208  // HTCAPTION here, because when the window is maximized the window controls
209  // will fall into this space (since the BrowserView is sized to entire size
210  // of the window at that point), and the HTCAPTION value will cause the
211  // window controls not to work. So we return HTNOWHERE so that the caller
212  // will hit-test the window controls before finally falling back to
213  // HTCAPTION.
214  bv_bounds = browser_view_->bounds();
215  bv_bounds.set_height(browser_view_->toolbar_->y());
216  if (bv_bounds.Contains(point))
217    return HTNOWHERE;
218
219  // If the point is somewhere else, delegate to the default implementation.
220  return browser_view_->views::ClientView::NonClientHitTest(point);
221}
222
223//////////////////////////////////////////////////////////////////////////////
224// BrowserViewLayout, views::LayoutManager implementation:
225
226void BrowserViewLayout::Installed(views::View* host) {
227  contents_split_ = NULL;
228  contents_container_ = NULL;
229  download_shelf_ = NULL;
230  active_bookmark_bar_ = NULL;
231  browser_view_ = static_cast<BrowserView*>(host);
232}
233
234void BrowserViewLayout::Uninstalled(views::View* host) {}
235
236void BrowserViewLayout::ViewAdded(views::View* host, views::View* view) {
237  switch (view->id()) {
238    case VIEW_ID_CONTENTS_SPLIT: {
239      contents_split_ = static_cast<views::SingleSplitView*>(view);
240      // We're installed as the LayoutManager before BrowserView creates the
241      // contents, so we have to set contents_container_ here rather than in
242      // Installed.
243      contents_container_ = browser_view_->contents_;
244      break;
245    }
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  }
253}
254
255void BrowserViewLayout::ViewRemoved(views::View* host, views::View* view) {
256  switch (view->id()) {
257    case VIEW_ID_BOOKMARK_BAR:
258      active_bookmark_bar_ = NULL;
259      break;
260  }
261}
262
263void BrowserViewLayout::Layout(views::View* host) {
264  // Showing Instant extended suggestions causes us to temporarily hide any
265  // visible bookmark bar and infobars.  In turn, this hiding would normally
266  // cause the content below the suggestions to shift upwards, which looks
267  // surprising (since from the user's perspective, we're "covering" rather than
268  // "removing" the bookmark bar/infobars).  To prevent this, we save off the
269  // content origin here, then once we finish laying things out, force the
270  // contents to continue to display from that origin.
271  const chrome::search::Mode& mode = browser()->search_model()->mode();
272  views::WebView* contents = browser_view_->contents_container_;
273  int overlay_height = contents_container_->overlay_height();
274  gfx::Point old_contents_origin;
275  if (overlay_height > 0 && mode.is_search_suggestions() &&
276      mode.is_origin_default()) {
277    old_contents_origin = contents->bounds().origin();
278    views::View::ConvertPointToTarget(contents->parent(), browser_view_,
279                                      &old_contents_origin);
280  }
281
282  vertical_layout_rect_ = browser_view_->GetLocalBounds();
283  int top = LayoutTabStripRegion();
284  if (browser_view_->IsTabStripVisible()) {
285    int x = browser_view_->tabstrip_->GetMirroredX() +
286        browser_view_->GetMirroredX() +
287        browser_view_->frame()->GetThemeBackgroundXInset();
288    browser_view_->tabstrip_->SetBackgroundOffset(gfx::Point(x,
289        browser_view_->frame()->GetTabStripInsets(false).top));
290  }
291  top = LayoutToolbar(top);
292  // TODO(jamescook): When immersive mode supports the bookmark bar this should
293  // move below.
294  browser_view_->top_container()->SetBounds(0, 0, browser_view_->width(), top);
295  top = LayoutBookmarkAndInfoBars(top);
296  // During immersive mode reveal the content stays near the top of the view.
297  if (browser_view_->immersive_mode_controller()->IsRevealed()) {
298    top = browser_view_->tabstrip_->y();
299    if (!browser_view_->immersive_mode_controller()->hide_tab_indicators())
300      top += TabStrip::GetImmersiveHeight();
301  }
302
303  int bottom = LayoutDownloadShelf(browser_view_->height());
304  int active_top_margin = GetTopMarginForActiveContent();
305  top -= active_top_margin;
306  contents_container_->SetActiveTopMargin(active_top_margin);
307  LayoutTabContents(top, bottom);
308
309  // Now set the contents to display at their previous origin if we just hid the
310  // bookmark and/or infobars.
311  if (active_top_margin == 0 && !old_contents_origin.IsOrigin()) {
312    gfx::Point new_contents_origin(contents->bounds().origin());
313    views::View::ConvertPointToTarget(contents->parent(), browser_view_,
314                                      &new_contents_origin);
315    active_top_margin = old_contents_origin.y() - new_contents_origin.y();
316    // Special case: While normally the suggestions appear to "cover" any
317    // bookmark/infobars, if the suggestions are very short, they might not
318    // fully cover that gap, and leaving the contents at their original height
319    // would leave an odd-looking blank space.  In this case, we allow the
320    // contents to go ahead and shift upward.
321    if (active_top_margin > 0 && active_top_margin < overlay_height)
322      contents_container_->SetActiveTopMargin(active_top_margin);
323  }
324
325  // This must be done _after_ we lay out the WebContents since this
326  // code calls back into us to find the bounding box the find bar
327  // must be laid out within, and that code depends on the
328  // TabContentsContainer's bounds being up to date.
329  if (browser()->HasFindBarController()) {
330    browser()->GetFindBarController()->find_bar()->MoveWindowIfNecessary(
331        gfx::Rect(), true);
332  }
333}
334
335// Return the preferred size which is the size required to give each
336// children their respective preferred size.
337gfx::Size BrowserViewLayout::GetPreferredSize(views::View* host) {
338  return gfx::Size();
339}
340
341//////////////////////////////////////////////////////////////////////////////
342// BrowserViewLayout, private:
343
344Browser* BrowserViewLayout::browser() {
345  return browser_view_->browser();
346}
347
348const Browser* BrowserViewLayout::browser() const {
349  return browser_view_->browser();
350}
351
352int BrowserViewLayout::LayoutTabStripRegion() {
353  TabStrip* tabstrip = browser_view_->tabstrip_;
354  if (!browser_view_->IsTabStripVisible()) {
355    tabstrip->SetVisible(false);
356    tabstrip->SetBounds(0, 0, 0, 0);
357    return 0;
358  }
359  // This retrieves the bounds for the tab strip based on whether or not we show
360  // anything to the left of it, like the incognito avatar.
361  gfx::Rect tabstrip_bounds(
362      browser_view_->frame()->GetBoundsForTabStrip(tabstrip));
363  gfx::Point tabstrip_origin(tabstrip_bounds.origin());
364  views::View::ConvertPointToTarget(browser_view_->parent(), browser_view_,
365                                    &tabstrip_origin);
366  tabstrip_bounds.set_origin(tabstrip_origin);
367
368  tabstrip->SetVisible(true);
369  tabstrip->SetBoundsRect(tabstrip_bounds);
370  int bottom = tabstrip_bounds.bottom();
371
372  // The metro window switcher sits at the far right edge of the tabstrip
373  // a |kWindowSwitcherOffsetX| pixels from the right edge.
374  // Only visible if there is more than one type of window to switch between.
375  // TODO(mad): update this code when more window types than just incognito
376  // and regular are available.
377  views::Button* switcher_button = browser_view_->window_switcher_button_;
378  if (switcher_button) {
379    if (browser()->profile()->HasOffTheRecordProfile() &&
380        chrome::FindBrowserWithProfile(
381            browser()->profile()->GetOriginalProfile(),
382            browser()->host_desktop_type()) != NULL) {
383      switcher_button->SetVisible(true);
384      int width = browser_view_->width();
385      gfx::Size ps = switcher_button->GetPreferredSize();
386      if (width > ps.width()) {
387        switcher_button->SetBounds(width - ps.width() - kWindowSwitcherOffsetX,
388                                   0,
389                                   ps.width(),
390                                   ps.height());
391      }
392    } else {
393      // We hide the button if the incognito profile is not alive.
394      // Note that Layout() is not called to all browser windows automatically
395      // when a profile goes away but we rely in the metro_driver.dll to call
396      // ::SetWindowPos( , .. SWP_SHOWWINDOW) which causes this function to
397      // be called again. This works both in showing or hidding the button.
398      switcher_button->SetVisible(false);
399    }
400  }
401
402  return bottom;
403}
404
405int BrowserViewLayout::LayoutToolbar(int top) {
406  ToolbarView* toolbar = browser_view_->toolbar_;
407  int browser_view_width = vertical_layout_rect_.width();
408  bool toolbar_visible = browser_view_->IsToolbarVisible();
409  toolbar->location_bar()->SetLocationEntryFocusable(toolbar_visible);
410  int y = top;
411  y -= (toolbar_visible && browser_view_->IsTabStripVisible()) ?
412        kToolbarTabStripVerticalOverlap : 0;
413  int height = toolbar_visible ? toolbar->GetPreferredSize().height() : 0;
414  toolbar->SetVisible(toolbar_visible);
415  toolbar->SetBounds(vertical_layout_rect_.x(), y, browser_view_width, height);
416
417  return y + height;
418}
419
420int BrowserViewLayout::LayoutBookmarkAndInfoBars(int top) {
421  constrained_window_top_y_ =
422      top + browser_view_->y() - kConstrainedWindowOverlap;
423  find_bar_y_ = top + browser_view_->y() - 1;
424  if (active_bookmark_bar_) {
425    // If we're showing the Bookmark bar in detached style, then we
426    // need to show any Info bar _above_ the Bookmark bar, since the
427    // Bookmark bar is styled to look like it's part of the page.
428    if (active_bookmark_bar_->IsDetached())
429      return LayoutBookmarkBar(LayoutInfoBar(top));
430    // Otherwise, Bookmark bar first, Info bar second.
431    top = std::max(browser_view_->toolbar_->bounds().bottom(),
432                   LayoutBookmarkBar(top));
433  }
434  find_bar_y_ = top + browser_view_->y() - 1;
435  return LayoutInfoBar(top);
436}
437
438int BrowserViewLayout::LayoutBookmarkBar(int top) {
439  DCHECK(active_bookmark_bar_);
440  int y = top;
441  if (!browser_view_->IsBookmarkBarVisible()) {
442    active_bookmark_bar_->SetVisible(false);
443    active_bookmark_bar_->SetBounds(0, y, browser_view_->width(), 0);
444    return y;
445  }
446
447  active_bookmark_bar_->set_infobar_visible(InfobarVisible());
448  int bookmark_bar_height = active_bookmark_bar_->GetPreferredSize().height();
449  y -= views::NonClientFrameView::kClientEdgeThickness +
450      active_bookmark_bar_->GetToolbarOverlap(false);
451  active_bookmark_bar_->SetVisible(true);
452  active_bookmark_bar_->SetBounds(vertical_layout_rect_.x(), y,
453                                  vertical_layout_rect_.width(),
454                                  bookmark_bar_height);
455  return y + bookmark_bar_height;
456}
457
458int BrowserViewLayout::LayoutInfoBar(int top) {
459  InfoBarContainerView* infobar_container = browser_view_->infobar_container_;
460  // Raise the |infobar_container| by its vertical overlap.
461  infobar_container->SetVisible(InfobarVisible());
462  int height;
463  int overlapped_top = top - infobar_container->GetVerticalOverlap(&height);
464  infobar_container->SetBounds(vertical_layout_rect_.x(),
465                               overlapped_top,
466                               vertical_layout_rect_.width(),
467                               height);
468  return overlapped_top + height;
469}
470
471void BrowserViewLayout::LayoutTabContents(int top, int bottom) {
472  // The ultimate idea is to calculate bounds and reserved areas for all
473  // contents views first and then resize them all, so every view
474  // (and its contents) is resized and laid out only once.
475
476  // The views hierarcy (see browser_view.h for more details):
477  // contents_split_ -> [contents_container_ | devtools]
478
479  gfx::Rect contents_bounds;
480  gfx::Rect devtools_bounds;
481
482  gfx::Rect contents_split_bounds(vertical_layout_rect_.x(), top,
483                                  vertical_layout_rect_.width(),
484                                  std::max(0, bottom - top));
485  gfx::Point contents_split_offset(
486      contents_split_bounds.x() - contents_split_->bounds().x(),
487      contents_split_bounds.y() - contents_split_->bounds().y());
488
489  // Layout resize corner and calculate reserved contents rects here as all
490  // contents view bounds are already determined, but not yet set at this point,
491  // so contents will be laid out once at most.
492  gfx::Rect browser_reserved_rect;
493  if (!browser_view_->frame_->IsMaximized() &&
494      !browser_view_->frame_->IsFullscreen()) {
495    gfx::Size resize_corner_size = browser_view_->GetResizeCornerSize();
496    if (!resize_corner_size.IsEmpty()) {
497      gfx::Rect bounds = browser_view_->GetContentsBounds();
498      gfx::Point resize_corner_origin(
499          bounds.right() - resize_corner_size.width(),
500          bounds.bottom() - resize_corner_size.height());
501      browser_reserved_rect =
502          gfx::Rect(resize_corner_origin, resize_corner_size);
503    }
504  }
505
506  // Now it's safe to actually resize all contents views in the hierarchy.
507  contents_split_->SetBoundsRect(contents_split_bounds);
508}
509
510int BrowserViewLayout::GetTopMarginForActiveContent() {
511  if (!active_bookmark_bar_ || !browser_view_->IsBookmarkBarVisible() ||
512      !active_bookmark_bar_->IsDetached()) {
513    return 0;
514  }
515
516  if (contents_split_->child_at(1) && contents_split_->child_at(1)->visible())
517    return 0;
518
519  // Adjust for separator.
520  return active_bookmark_bar_->height() -
521      views::NonClientFrameView::kClientEdgeThickness;
522}
523
524int BrowserViewLayout::LayoutDownloadShelf(int bottom) {
525  // Re-layout the shelf either if it is visible or if its close animation
526  // is currently running.
527  if (browser_view_->IsDownloadShelfVisible() ||
528      (download_shelf_ && download_shelf_->IsClosing())) {
529    bool visible = browser()->SupportsWindowFeature(
530        Browser::FEATURE_DOWNLOADSHELF);
531    DCHECK(download_shelf_);
532    int height = visible ? download_shelf_->GetPreferredSize().height() : 0;
533    download_shelf_->SetVisible(visible);
534    download_shelf_->SetBounds(vertical_layout_rect_.x(), bottom - height,
535                               vertical_layout_rect_.width(), height);
536    download_shelf_->Layout();
537    bottom -= height;
538  }
539  return bottom;
540}
541
542bool BrowserViewLayout::InfobarVisible() const {
543  views::View* infobar_container = browser_view_->infobar_container_;
544  // NOTE: Can't check if the size IsEmpty() since it's always 0-width.
545  return browser()->SupportsWindowFeature(Browser::FEATURE_INFOBAR) &&
546      (infobar_container->GetPreferredSize().height() != 0);
547}
548