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