// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/bubble/bubble_frame_view.h"

#include <algorithm>
#include <utility>

#include "build/build_config.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/default_style.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/path.h"
#include "ui/gfx/skia_util.h"
#include "ui/native_theme/native_theme.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/paint_info.h"
#include "ui/views/resources/grit/views_resources.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/client_view.h"
#include "ui/views/window/dialog_delegate.h"

namespace views {

namespace {

// Background color of the footnote view.
constexpr SkColor kFootnoteBackgroundColor = SkColorSetRGB(250, 250, 250);

// Color of the top border of the footnote.
constexpr SkColor kFootnoteBorderColor = SkColorSetRGB(235, 235, 235);

// Get the |vertical| or horizontal amount that |available_bounds| overflows
// |window_bounds|.
int GetOffScreenLength(const gfx::Rect& available_bounds,
                       const gfx::Rect& window_bounds,
                       bool vertical) {
  if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
    return 0;

  //  window_bounds
  //  +---------------------------------+
  //  |             top                 |
  //  |      +------------------+       |
  //  | left | available_bounds | right |
  //  |      +------------------+       |
  //  |            bottom               |
  //  +---------------------------------+
  if (vertical)
    return std::max(0, available_bounds.y() - window_bounds.y()) +
           std::max(0, window_bounds.bottom() - available_bounds.bottom());
  return std::max(0, available_bounds.x() - window_bounds.x()) +
         std::max(0, window_bounds.right() - available_bounds.right());
}

}  // namespace

// A container that changes visibility with its contents.
class FootnoteContainerView : public View {
 public:
  FootnoteContainerView() {}

  // View:
  void ChildVisibilityChanged(View* child) override {
    DCHECK_EQ(child_count(), 1);
    SetVisible(child->visible());
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(FootnoteContainerView);
};

// static
const char BubbleFrameView::kViewClassName[] = "BubbleFrameView";

BubbleFrameView::BubbleFrameView(const gfx::Insets& title_margins,
                                 const gfx::Insets& content_margins)
    : bubble_border_(nullptr),
      title_margins_(title_margins),
      content_margins_(content_margins),
      footnote_margins_(content_margins_),
      title_icon_(new views::ImageView()),
      default_title_(CreateDefaultTitleLabel(base::string16()).release()),
      custom_title_(nullptr),
      close_(nullptr),
      footnote_container_(nullptr),
      close_button_clicked_(false) {
  AddChildView(title_icon_);

  default_title_->SetVisible(false);
  AddChildView(default_title_);

  close_ = CreateCloseButton(this);
  close_->SetVisible(false);
#if defined(OS_WIN)
  // Windows will automatically create a tooltip for the close button based on
  // the HTCLOSE result from NonClientHitTest().
  close_->SetTooltipText(base::string16());
#endif
  AddChildView(close_);
}

BubbleFrameView::~BubbleFrameView() {}

// static
std::unique_ptr<Label> BubbleFrameView::CreateDefaultTitleLabel(
    const base::string16& title_text) {
  auto title = std::make_unique<Label>(title_text, style::CONTEXT_DIALOG_TITLE);
  title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  title->set_collapse_when_hidden(true);
  title->SetMultiLine(true);
  return title;
}

// static
Button* BubbleFrameView::CreateCloseButton(ButtonListener* listener) {
  ImageButton* close_button = nullptr;
  if (ui::MaterialDesignController::IsSecondaryUiMaterial()) {
    close_button = CreateVectorImageButton(listener);
    SetImageFromVectorIcon(close_button, vector_icons::kClose16Icon);
  } else {
    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
    close_button = new ImageButton(listener);
    close_button->SetImage(Button::STATE_NORMAL,
                           *rb->GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia());
    close_button->SetImage(
        Button::STATE_HOVERED,
        *rb->GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia());
    close_button->SetImage(
        Button::STATE_PRESSED,
        *rb->GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia());
  }
  close_button->SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_CLOSE));
  close_button->SizeToPreferredSize();

  // Remove the close button from tab traversal on all platforms. Note this does
  // not affect screen readers' ability to focus the close button. Keyboard
  // access to the close button when not using a screen reader is done via the
  // ESC key handler in DialogClientView.
  close_button->SetFocusBehavior(View::FocusBehavior::NEVER);
  return close_button;
}

gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
  // When NonClientView asks for this, the size of the frame view has been set
  // (i.e. |this|), but not the client view bounds.
  gfx::Rect client_bounds = GetContentsBounds();
  client_bounds.Inset(GetClientInsetsForFrameWidth(client_bounds.width()));
  // Only account for footnote_container_'s height if it's visible, because
  // content_margins_ adds extra padding even if all child views are invisible.
  if (footnote_container_ && footnote_container_->visible()) {
    client_bounds.set_height(client_bounds.height() -
                             footnote_container_->height());
  }
  return client_bounds;
}

gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds(
    const gfx::Rect& client_bounds) const {
  gfx::Size size(GetFrameSizeForClientSize(client_bounds.size()));
  return bubble_border_->GetBounds(gfx::Rect(), size);
}

bool BubbleFrameView::GetClientMask(const gfx::Size& size,
                                    gfx::Path* path) const {
  // NonClientView calls this after setting the client view size from the return
  // of GetBoundsForClientView(); feeding it back in |size|.
  DCHECK(GetBoundsForClientView().size() == size);
  DCHECK(GetWidget()->client_view()->size() == size);

  const int radius = bubble_border_->GetBorderCornerRadius();
  gfx::Insets content_insets = GetInsets();
  // If the client bounds don't touch the edges, no need to mask.
  if (std::min({content_insets.top(), content_insets.left(),
                content_insets.bottom(), content_insets.right()}) > radius) {
    return false;
  }
  gfx::RectF rect((gfx::Rect(size)));
  path->addRoundRect(gfx::RectFToSkRect(rect), radius, radius);
  return true;
}

int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
  if (!bounds().Contains(point))
    return HTNOWHERE;
  if (close_->visible() && close_->GetMirroredBounds().Contains(point))
    return HTCLOSE;

  // Allow dialogs to show the system menu and be dragged.
  if (GetWidget()->widget_delegate()->AsDialogDelegate() &&
      !GetWidget()->widget_delegate()->AsBubbleDialogDelegate()) {
    gfx::Rect bounds(GetContentsBounds());
    bounds.Inset(title_margins_);
    gfx::Rect sys_rect(0, 0, bounds.x(), bounds.y());
    sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0));
    if (sys_rect.Contains(point))
      return HTSYSMENU;
    if (point.y() < title()->bounds().bottom())
      return HTCAPTION;
  }

  return GetWidget()->client_view()->NonClientHitTest(point);
}

void BubbleFrameView::GetWindowMask(const gfx::Size& size,
                                    gfx::Path* window_mask) {
  if (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
      bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER &&
      bubble_border_->shadow() != BubbleBorder::NO_ASSETS)
    return;

  // We don't return a mask for windows with arrows unless they use
  // BubbleBorder::NO_ASSETS.
  if (bubble_border_->shadow() != BubbleBorder::NO_ASSETS &&
      bubble_border_->arrow() != BubbleBorder::NONE &&
      bubble_border_->arrow() != BubbleBorder::FLOAT)
    return;

  // Use a window mask roughly matching the border in the image assets.
  const int kBorderStrokeSize =
      bubble_border_->shadow() == BubbleBorder::NO_ASSETS ? 0 : 1;
  const SkScalar kCornerRadius =
      SkIntToScalar(bubble_border_->GetBorderCornerRadius());
  const gfx::Insets border_insets = bubble_border_->GetInsets();
  SkRect rect = {
      SkIntToScalar(border_insets.left() - kBorderStrokeSize),
      SkIntToScalar(border_insets.top() - kBorderStrokeSize),
      SkIntToScalar(size.width() - border_insets.right() + kBorderStrokeSize),
      SkIntToScalar(size.height() - border_insets.bottom() +
                    kBorderStrokeSize)};

  if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER ||
      bubble_border_->shadow() == BubbleBorder::NO_ASSETS) {
    window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius);
  } else {
    static const int kBottomBorderShadowSize = 2;
    rect.fBottom += SkIntToScalar(kBottomBorderShadowSize);
    window_mask->addRect(rect);
  }
  gfx::Path arrow_path;
  if (bubble_border_->GetArrowPath(gfx::Rect(size), &arrow_path))
    window_mask->addPath(arrow_path, 0, 0);
}

void BubbleFrameView::ResetWindowControls() {
  close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
}

void BubbleFrameView::UpdateWindowIcon() {
  gfx::ImageSkia image;
  if (GetWidget()->widget_delegate()->ShouldShowWindowIcon())
    image = GetWidget()->widget_delegate()->GetWindowIcon();
  title_icon_->SetImage(&image);
}

void BubbleFrameView::UpdateWindowTitle() {
  if (default_title_) {
    const WidgetDelegate* delegate = GetWidget()->widget_delegate();
    default_title_->SetVisible(delegate->ShouldShowWindowTitle());
    default_title_->SetText(delegate->GetWindowTitle());
  }  // custom_title_'s updates are handled by its creator.
}

void BubbleFrameView::SizeConstraintsChanged() {}

void BubbleFrameView::SetTitleView(std::unique_ptr<View> title_view) {
  DCHECK(title_view);
  delete default_title_;
  default_title_ = nullptr;
  delete custom_title_;
  custom_title_ = title_view.get();
  // Keep the title after the icon for focus order.
  AddChildViewAt(title_view.release(), 1);
}

const char* BubbleFrameView::GetClassName() const {
  return kViewClassName;
}

gfx::Insets BubbleFrameView::GetInsets() const {
  return GetClientInsetsForFrameWidth(GetContentsBounds().width());
}

gfx::Size BubbleFrameView::CalculatePreferredSize() const {
  // Get the preferred size of the client area.
  gfx::Size client_size = GetWidget()->client_view()->GetPreferredSize();
  // Expand it to include the bubble border and space for the arrow.
  return GetWindowBoundsForClientBounds(gfx::Rect(client_size)).size();
}

gfx::Size BubbleFrameView::GetMinimumSize() const {
  // Get the minimum size of the client area.
  gfx::Size client_size = GetWidget()->client_view()->GetMinimumSize();
  // Expand it to include the bubble border and space for the arrow.
  return GetWindowBoundsForClientBounds(gfx::Rect(client_size)).size();
}

gfx::Size BubbleFrameView::GetMaximumSize() const {
#if defined(OS_WIN)
  // On Windows, this causes problems, so do not set a maximum size (it doesn't
  // take the drop shadow area into account, resulting in a too-small window;
  // see http://crbug.com/506206). This isn't necessary on Windows anyway, since
  // the OS doesn't give the user controls to resize a bubble.
  return gfx::Size();
#else
#if defined(OS_MACOSX)
  // Allow BubbleFrameView dialogs to be resizable on Mac.
  if (GetWidget()->widget_delegate()->CanResize()) {
    gfx::Size client_size = GetWidget()->client_view()->GetMaximumSize();
    if (client_size.IsEmpty())
      return client_size;
    return GetWindowBoundsForClientBounds(gfx::Rect(client_size)).size();
  }
#endif  // OS_MACOSX
  // Non-dialog bubbles should be non-resizable, so its max size is its
  // preferred size.
  return GetPreferredSize();
#endif
}

void BubbleFrameView::Layout() {
  // The title margins may not be set, but make sure that's only the case when
  // there's no title.
  DCHECK(!title_margins_.IsEmpty() ||
         (!custom_title_ && !default_title_->visible()));

  const gfx::Rect contents_bounds = GetContentsBounds();
  gfx::Rect bounds = contents_bounds;
  bounds.Inset(title_margins_);
  if (bounds.IsEmpty())
    return;

  int title_label_right = bounds.right();
  if (close_->visible()) {
    // The close button is positioned somewhat closer to the edge of the bubble.
    const int close_margin =
        LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
    close_->SetPosition(
        gfx::Point(contents_bounds.right() - close_margin - close_->width(),
                   contents_bounds.y() + close_margin));
    title_label_right = std::min(title_label_right, close_->x() - close_margin);
  }

  gfx::Size title_icon_pref_size(title_icon_->GetPreferredSize());
  const int title_icon_padding =
      title_icon_pref_size.width() > 0 ? title_margins_.left() : 0;
  const int title_label_x =
      bounds.x() + title_icon_pref_size.width() + title_icon_padding;

  // TODO(tapted): Layout() should skip more surrounding code when !HasTitle().
  // Currently DCHECKs fail since title_insets is 0 when there is no title.
  if (DCHECK_IS_ON() && HasTitle()) {
    gfx::Insets title_insets = GetTitleLabelInsetsFromFrame();
    if (border())
      title_insets += border()->GetInsets();
    DCHECK_EQ(title_insets.left(), title_label_x);
    DCHECK_EQ(title_insets.right(), width() - title_label_right);
  }

  const int title_available_width =
      std::max(1, title_label_right - title_label_x);
  const int title_preferred_height =
      title()->GetHeightForWidth(title_available_width);
  const int title_height =
      std::max(title_icon_pref_size.height(), title_preferred_height);
  title()->SetBounds(title_label_x,
                     bounds.y() + (title_height - title_preferred_height) / 2,
                     title_available_width, title_preferred_height);

  title_icon_->SetBounds(bounds.x(), bounds.y(), title_icon_pref_size.width(),
                         title_height);

  // Only account for footnote_container_'s height if it's visible, because
  // content_margins_ adds extra padding even if all child views are invisible.
  if (footnote_container_ && footnote_container_->visible()) {
    const int width = contents_bounds.width();
    const int height = footnote_container_->GetHeightForWidth(width);
    footnote_container_->SetBounds(
        contents_bounds.x(), contents_bounds.bottom() - height, width, height);
  }
}

void BubbleFrameView::OnThemeChanged() {
  UpdateWindowTitle();
  ResetWindowControls();
  UpdateWindowIcon();
}

void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
  if (bubble_border_ && bubble_border_->use_theme_background_color()) {
    bubble_border_->set_background_color(GetNativeTheme()->
        GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
    SchedulePaint();
  }
}

void BubbleFrameView::ViewHierarchyChanged(
    const ViewHierarchyChangedDetails& details) {
  if (!details.is_add && details.parent == footnote_container_ &&
      footnote_container_->child_count() == 1 &&
      details.child == footnote_container_->child_at(0)) {
    // Setting the footnote_container_ to be hidden and null it. This will
    // remove update the bubble to have no placeholder for the footnote and
    // enable the destructor to delete the footnote_container_ later.
    footnote_container_->SetVisible(false);
    footnote_container_ = nullptr;
  }
}

void BubbleFrameView::OnPaint(gfx::Canvas* canvas) {
  OnPaintBackground(canvas);
  // Border comes after children.
}

void BubbleFrameView::PaintChildren(const PaintInfo& paint_info) {
  NonClientFrameView::PaintChildren(paint_info);

  ui::PaintCache paint_cache;
  ui::PaintRecorder recorder(
      paint_info.context(), paint_info.paint_recording_size(),
      paint_info.paint_recording_scale_x(),
      paint_info.paint_recording_scale_y(), &paint_cache);
  OnPaintBorder(recorder.canvas());
}

void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) {
  if (sender == close_) {
    close_button_clicked_ = true;
    GetWidget()->Close();
  }
}

void BubbleFrameView::SetBubbleBorder(std::unique_ptr<BubbleBorder> border) {
  bubble_border_ = border.get();
  SetBorder(std::move(border));

  // Update the background, which relies on the border.
  SetBackground(std::make_unique<views::BubbleBackground>(bubble_border_));
}

void BubbleFrameView::SetFootnoteView(View* view) {
  if (!view)
    return;

  DCHECK(!footnote_container_);
  footnote_container_ = new FootnoteContainerView();
  footnote_container_->SetLayoutManager(
      std::make_unique<BoxLayout>(BoxLayout::kVertical, footnote_margins_, 0));
  footnote_container_->SetBackground(
      CreateSolidBackground(kFootnoteBackgroundColor));
  footnote_container_->SetBorder(
      CreateSolidSidedBorder(1, 0, 0, 0, kFootnoteBorderColor));
  footnote_container_->AddChildView(view);
  footnote_container_->SetVisible(view->visible());
  AddChildView(footnote_container_);
}

gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
                                                  const gfx::Size& client_size,
                                                  bool adjust_if_offscreen) {
  gfx::Size size(GetFrameSizeForClientSize(client_size));

  const BubbleBorder::Arrow arrow = bubble_border_->arrow();
  if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) {
    // Try to mirror the anchoring if the bubble does not fit on the screen.
    if (!bubble_border_->is_arrow_at_center(arrow)) {
      MirrorArrowIfOffScreen(true, anchor_rect, size);
      MirrorArrowIfOffScreen(false, anchor_rect, size);
    } else {
      const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow);
      MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size);
      OffsetArrowIfOffScreen(anchor_rect, size);
    }
  }

  // Calculate the bounds with the arrow in its updated location and offset.
  return bubble_border_->GetBounds(anchor_rect, size);
}

gfx::Rect BubbleFrameView::GetAvailableScreenBounds(
    const gfx::Rect& rect) const {
  // The bubble attempts to fit within the current screen bounds.
  return display::Screen::GetScreen()
      ->GetDisplayNearestPoint(rect.CenterPoint())
      .work_area();
}

bool BubbleFrameView::ExtendClientIntoTitle() const {
  return false;
}

bool BubbleFrameView::IsCloseButtonVisible() const {
  return close_->visible();
}

gfx::Rect BubbleFrameView::GetCloseButtonMirroredBounds() const {
  return close_->GetMirroredBounds();
}

void BubbleFrameView::MirrorArrowIfOffScreen(
    bool vertical,
    const gfx::Rect& anchor_rect,
    const gfx::Size& client_size) {
  // Check if the bounds don't fit on screen.
  gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
  gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
  if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) {
    BubbleBorder::Arrow arrow = bubble_border()->arrow();
    // Mirror the arrow and get the new bounds.
    bubble_border_->set_arrow(
        vertical ? BubbleBorder::vertical_mirror(arrow) :
                   BubbleBorder::horizontal_mirror(arrow));
    gfx::Rect mirror_bounds =
        bubble_border_->GetBounds(anchor_rect, client_size);
    // Restore the original arrow if mirroring doesn't show more of the bubble.
    // Otherwise it should invoke parent's Layout() to layout the content based
    // on the new bubble border.
    if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >=
        GetOffScreenLength(available_bounds, window_bounds, vertical)) {
      bubble_border_->set_arrow(arrow);
    } else {
      if (parent())
        parent()->Layout();
      SchedulePaint();
    }
  }
}

void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
                                             const gfx::Size& client_size) {
  BubbleBorder::Arrow arrow = bubble_border()->arrow();
  DCHECK(BubbleBorder::is_arrow_at_center(arrow));

  // Get the desired bubble bounds without adjustment.
  bubble_border_->set_arrow_offset(0);
  gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));

  gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
  if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
    return;

  // Calculate off-screen adjustment.
  const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow);
  int offscreen_adjust = 0;
  if (is_horizontal) {
    if (window_bounds.x() < available_bounds.x())
      offscreen_adjust = available_bounds.x() - window_bounds.x();
    else if (window_bounds.right() > available_bounds.right())
      offscreen_adjust = available_bounds.right() - window_bounds.right();
  } else {
    if (window_bounds.y() < available_bounds.y())
      offscreen_adjust = available_bounds.y() - window_bounds.y();
    else if (window_bounds.bottom() > available_bounds.bottom())
      offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
  }

  // For center arrows, arrows are moved in the opposite direction of
  // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
  // window needs to be moved to the right and that means we need to move arrow
  // to the left, and that means negative offset.
  bubble_border_->set_arrow_offset(
      bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust);
  if (offscreen_adjust)
    SchedulePaint();
}

int BubbleFrameView::GetFrameWidthForClientWidth(int client_width) const {
  // Note that GetMinimumSize() for multiline Labels is typically 0.
  const int title_bar_width = title()->GetMinimumSize().width() +
                              GetTitleLabelInsetsFromFrame().width();
  const int client_area_width = client_width + content_margins_.width();
  const int frame_width = std::max(title_bar_width, client_area_width);
  DialogDelegate* dialog_delegate =
      GetWidget()->widget_delegate()->AsDialogDelegate();
  return dialog_delegate && dialog_delegate->ShouldSnapFrameWidth()
             ? LayoutProvider::Get()->GetSnappedDialogWidth(frame_width)
             : frame_width;
}

gfx::Size BubbleFrameView::GetFrameSizeForClientSize(
    const gfx::Size& client_size) const {
  const int frame_width = GetFrameWidthForClientWidth(client_size.width());
  const gfx::Insets client_insets = GetClientInsetsForFrameWidth(frame_width);
  DCHECK_GE(frame_width, client_size.width());
  gfx::Size size(frame_width, client_size.height() + client_insets.height());

  // Only account for footnote_container_'s height if it's visible, because
  // content_margins_ adds extra padding even if all child views are invisible.
  if (footnote_container_ && footnote_container_->visible())
    size.Enlarge(0, footnote_container_->GetHeightForWidth(size.width()));

  return size;
}

bool BubbleFrameView::HasTitle() const {
  return (custom_title_ != nullptr &&
          GetWidget()->widget_delegate()->ShouldShowWindowTitle()) ||
         (default_title_ != nullptr &&
          default_title_->GetPreferredSize().height() > 0) ||
         title_icon_->GetPreferredSize().height() > 0;
}

gfx::Insets BubbleFrameView::GetTitleLabelInsetsFromFrame() const {
  int insets_right = 0;
  if (GetWidget()->widget_delegate()->ShouldShowCloseButton()) {
    const int close_margin =
        LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
    insets_right = 2 * close_margin + close_->width();
  }
  if (!HasTitle())
    return gfx::Insets(0, 0, 0, insets_right);

  insets_right = std::max(insets_right, title_margins_.right());
  const gfx::Size title_icon_pref_size = title_icon_->GetPreferredSize();
  const int title_icon_padding =
      title_icon_pref_size.width() > 0 ? title_margins_.left() : 0;
  const int insets_left =
      title_margins_.left() + title_icon_pref_size.width() + title_icon_padding;
  return gfx::Insets(title_margins_.top(), insets_left, title_margins_.bottom(),
                     insets_right);
}

gfx::Insets BubbleFrameView::GetClientInsetsForFrameWidth(
    int frame_width) const {
  int close_height = 0;
  if (!ExtendClientIntoTitle() &&
      GetWidget()->widget_delegate()->ShouldShowCloseButton()) {
    const int close_margin =
        LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
    // Note: |close_margin| is not applied on the bottom of the icon.
    close_height = close_margin + close_->height();
  }
  if (!HasTitle())
    return content_margins_ + gfx::Insets(close_height, 0, 0, 0);

  const int icon_height = title_icon_->GetPreferredSize().height();
  const int label_height = title()->GetHeightForWidth(
      frame_width - GetTitleLabelInsetsFromFrame().width());
  const int title_height =
      std::max(icon_height, label_height) + title_margins_.height();
  return content_margins_ +
         gfx::Insets(std::max(title_height, close_height), 0, 0, 0);
}

}  // namespace views
