chromium/chrome/browser/chromeos/policy/dlp/clipboard_bubble.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/policy/dlp/clipboard_bubble.h"

#include "base/functional/bind.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_clipboard_bubble_constants.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_policy_constants.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_utils.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/styled_label.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/style/color_provider.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "ui/chromeos/styles/cros_styles.h"
#include "ui/native_theme/native_theme_aura.h"
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

namespace policy {

namespace {

// The corner radius of the bubble.
constexpr int kBubbleCornerRadius = 8;
constexpr gfx::RoundedCornersF kCornerRadii(kBubbleCornerRadius);

// The blur radius for the bubble background.
constexpr int kBubbleBlurRadius = 80;

// The size of the managed icon.
constexpr int kManagedIconSize = 20;

// The maximum width of the bubble.
constexpr int kBubbleWidth = 360;

// The spacing between the icon and label in the bubble.
constexpr int kIconLabelSpacing = 16;

// The padding which separates the bubble border with its inner contents.
constexpr int kBubblePadding = 16;

// The line height of the bubble text.
constexpr int kLineHeight = 20;

// The insets of the bubble borders.
constexpr gfx::Insets kBubbleBorderInsets(1);

// The font name of the text used in the bubble.
constexpr char kTextFontName[] = "Roboto";

// The font size of the text used in the bubble.
constexpr int kTextFontSize = 13;

// The height of the dismiss button.
constexpr int kButtonHeight = 32;

// The padding which separates the button border with its inner contents.
constexpr int kButtonPadding = 16;

// The spacing between the button border and label.
constexpr int kButtonLabelSpacing = 8;

// The spacing between the buttons.
constexpr int kButtonsSpacing = 8;

#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/1311180) Replace color retrieval with more long term solution.
SkColor RetrieveColor(cros_styles::ColorName name) {
  return cros_styles::ResolveColor(
      name, ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors(),
      /*use_debug_colors=*/false);
}
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

class BubbleButton : public views::LabelButton {
  METADATA_HEADER(BubbleButton, views::LabelButton)

 public:
  explicit BubbleButton(const std::u16string& button_label) {
    SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);

    SetText(button_label);

    const gfx::FontList font_list = GetFontList();
    label()->SetFontList(font_list);

#if BUILDFLAG(IS_CHROMEOS_ASH)
    const SkColor text_color = ash::ColorProvider::Get()->GetContentLayerColor(
        ash::ColorProvider::ContentLayerType::kButtonLabelColorBlue);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
    // TODO(crbug.com/1311180) Replace color retrieval with more long term
    // solution.
    const SkColor text_color =
        ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()
            ? gfx::kGoogleBlue300
            : gfx::kGoogleBlue600;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
    SetTextColor(ButtonState::STATE_NORMAL, text_color);
    SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
    SetSize({gfx::GetStringWidth(button_label, font_list) + 2 * kButtonPadding,
             kButtonHeight});
  }

  BubbleButton(const BubbleButton&) = delete;
  BubbleButton& operator=(const BubbleButton&) = delete;
  ~BubbleButton() override = default;

  int GetLabelWidth() const { return label()->bounds().width(); }

  static gfx::FontList GetFontList() {
    return gfx::FontList({kTextFontName}, gfx::Font::NORMAL, kTextFontSize,
                         gfx::Font::Weight::MEDIUM);
  }
};

void OnLearnMoreLinkClicked() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  ash::NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(dlp::kDlpLearnMoreUrl),
      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      ash::NewWindowDelegate::Disposition::kNewForegroundTab);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
  // The dlp policy applies to the main profile, so use the main profile for
  // opening the page.
  NavigateParams navigate_params(
      ProfileManager::GetPrimaryUserProfile(), GURL(dlp::kDlpLearnMoreUrl),
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                ui::PAGE_TRANSITION_FROM_API));
  navigate_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  navigate_params.window_action = NavigateParams::SHOW_WINDOW;
  Navigate(&navigate_params);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

}  // namespace

BEGIN_METADATA(BubbleButton)
ADD_READONLY_PROPERTY_METADATA(int, LabelWidth)
END_METADATA

ClipboardBubbleView::ClipboardBubbleView(const std::u16string& text) {
  SetPaintToLayer(ui::LAYER_SOLID_COLOR);
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  // TODO(crbug.com/1311180) Replace color retrieval with more long term
  // solution.
  layer()->SetColor(RetrieveColor(cros_styles::ColorName::kBgColor));
  layer()->SetBackgroundBlur(kBubbleBlurRadius);
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
  layer()->SetBackgroundBlur(kBubbleBlurRadius);
  layer()->SetRoundedCornerRadius(kCornerRadii);

  // Add the managed icon.
#if BUILDFLAG(IS_CHROMEOS_ASH)
  ash::ColorProvider* color_provider = ash::ColorProvider::Get();
  const SkColor icon_color = color_provider->GetContentLayerColor(
      ash::ColorProvider::ContentLayerType::kIconColorPrimary);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
  // TODO(crbug.com/1311180) Replace color retrieval with more long term
  // solution.
  const SkColor icon_color =
      RetrieveColor(cros_styles::ColorName::kIconColorPrimary);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
  managed_icon_ = AddChildView(std::make_unique<views::ImageView>());
  managed_icon_->SetPaintToLayer();
  managed_icon_->layer()->SetFillsBoundsOpaquely(false);
  managed_icon_->SetBounds(kBubblePadding, kBubblePadding, kManagedIconSize,
                           kManagedIconSize);
  managed_icon_->SetImage(gfx::CreateVectorIcon(vector_icons::kBusinessIcon,
                                                kManagedIconSize, icon_color));

  // Add the bubble text.
  label_ = AddChildView(std::make_unique<views::StyledLabel>());
  label_->SetPaintToLayer();
  label_->layer()->SetFillsBoundsOpaquely(false);
  label_->SetPosition(gfx::Point(
      kBubblePadding + kManagedIconSize + kIconLabelSpacing, kBubblePadding));
  label_->SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT);

  std::u16string learn_more_link_text =
      l10n_util::GetStringUTF16(IDS_LEARN_MORE);
  std::u16string full_text = l10n_util::GetStringFUTF16(
      IDS_POLICY_DLP_CLIPBOARD_BUBBLE_MESSAGE, text, learn_more_link_text);
  const int main_message_length =
      full_text.size() - learn_more_link_text.size();

  // Set the styling of the main text.
  // TODO(crbug.com/1150741): Handle RTL.
  views::StyledLabel::RangeStyleInfo message_style;
#if BUILDFLAG(IS_CHROMEOS_ASH)
  message_style.override_color = color_provider->GetContentLayerColor(
      ash::ColorProvider::ContentLayerType::kTextColorPrimary);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
  // TODO(crbug.com/1311180) Replace color retrieval with more long term
  // solution.
  message_style.override_color =
      RetrieveColor(cros_styles::ColorName::kTextColorPrimary);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  label_->SetText(full_text);
  label_->AddStyleRange(gfx::Range(0, main_message_length), message_style);

  // Add "Learn more" link.
  views::StyledLabel::RangeStyleInfo link_style =
      views::StyledLabel::RangeStyleInfo::CreateForLink(
          base::BindRepeating(&OnLearnMoreLinkClicked));
#if BUILDFLAG(IS_CHROMEOS_ASH)
  link_style.override_color = color_provider->GetContentLayerColor(
      ash::ColorProvider::ContentLayerType::kTextColorURL);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
  // TODO(crbug.com/1311180) Replace color retrieval with more long term
  // solution.
  link_style.override_color =
      ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()
          ? gfx::kGoogleBlue300
          : gfx::kGoogleBlue600;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  label_->AddStyleRange(gfx::Range(main_message_length, full_text.size()),
                        link_style);
  label_->SetLineHeight(kLineHeight);
  label_->SizeToFit(kBubbleWidth - 2 * kBubblePadding - kManagedIconSize -
                    kIconLabelSpacing);
  label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);

  // Bubble borders
  border_ = AddChildView(std::make_unique<views::ImageView>());
  border_->SetPaintToLayer();
  border_->layer()->SetFillsBoundsOpaquely(false);
  auto shadow_border = std::make_unique<views::BubbleBorder>(
      views::BubbleBorder::FLOAT, views::BubbleBorder::STANDARD_SHADOW);
  shadow_border->SetCornerRadius(kBubbleCornerRadius);
  shadow_border->SetColor(SK_ColorTRANSPARENT);
  shadow_border->set_insets(kBubbleBorderInsets);
  border_->SetSize({kBubbleWidth, INT_MAX});
  border_->SetBorder(std::move(shadow_border));
  border_->SetCanProcessEventsWithinSubtree(false);
}

ClipboardBubbleView::~ClipboardBubbleView() = default;

void ClipboardBubbleView::OnThemeChanged() {
  views::View::OnThemeChanged();
#if BUILDFLAG(IS_CHROMEOS_ASH)
  const SkColor background_color =
      GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemBaseElevated);
  layer()->SetColor(background_color);
  label_->SetDisplayedOnBackgroundColor(background_color);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

void ClipboardBubbleView::UpdateBorderSize(const gfx::Size& size) {
  border_->SetSize(size);
}

BEGIN_METADATA(ClipboardBubbleView)
ADD_READONLY_PROPERTY_METADATA(gfx::Size, BubbleSize)
END_METADATA

ClipboardBlockBubble::ClipboardBlockBubble(const std::u16string& text)
    : ClipboardBubbleView(text) {
  // Add "Got it" button.
  std::u16string button_label =
      l10n_util::GetStringUTF16(IDS_POLICY_DLP_CLIPBOARD_BLOCK_DISMISS_BUTTON);
  button_ = AddChildView(std::make_unique<BubbleButton>(button_label));
  button_->SetPaintToLayer();
  button_->layer()->SetFillsBoundsOpaquely(false);
  button_->SetPosition(
      gfx::Point(kBubbleWidth - kBubblePadding - button_->width(),
                 kBubblePadding + label_->height() + kButtonLabelSpacing));

  UpdateBorderSize(GetBubbleSize());
}

ClipboardBlockBubble::~ClipboardBlockBubble() = default;

gfx::Size ClipboardBlockBubble::GetBubbleSize() const {
  DCHECK(label_);
  DCHECK(button_);
  return {kBubbleWidth, 2 * kBubblePadding + label_->bounds().height() +
                            kButtonLabelSpacing + button_->height()};
}

void ClipboardBlockBubble::SetDismissCallback(base::OnceClosure cb) {
  DCHECK(button_);
  button_->SetCallback(std::move(cb));
}

BEGIN_METADATA(ClipboardBlockBubble)
END_METADATA

ClipboardWarnBubble::ClipboardWarnBubble(const std::u16string& text)
    : ClipboardBubbleView(text) {
  // Add paste button.
  std::u16string paste_label =
      l10n_util::GetStringUTF16(IDS_POLICY_DLP_CLIPBOARD_WARN_PROCEED_BUTTON);
  paste_button_ = AddChildView(std::make_unique<BubbleButton>(paste_label));
  paste_button_->SetPaintToLayer();
  paste_button_->layer()->SetFillsBoundsOpaquely(false);
  paste_button_->SetPosition(
      gfx::Point(kBubbleWidth - kBubblePadding - paste_button_->width(),
                 kBubblePadding + label_->height() + kButtonLabelSpacing));

  // Add cancel button.
  std::u16string cancel_label =
      l10n_util::GetStringUTF16(IDS_POLICY_DLP_WARN_CANCEL_BUTTON);
  cancel_button_ = AddChildView(std::make_unique<BubbleButton>(cancel_label));
  cancel_button_->SetPaintToLayer();
  cancel_button_->layer()->SetFillsBoundsOpaquely(false);
  cancel_button_->SetPosition(
      gfx::Point(kBubbleWidth - kBubblePadding - paste_button_->width() -
                     kButtonsSpacing - cancel_button_->width(),
                 kBubblePadding + label_->height() + kButtonLabelSpacing));

  UpdateBorderSize(GetBubbleSize());
}

ClipboardWarnBubble::~ClipboardWarnBubble() {
  if (paste_cb_) {
    std::move(paste_cb_).Run(false);
  }
}

gfx::Size ClipboardWarnBubble::GetBubbleSize() const {
  DCHECK(label_);
  DCHECK(cancel_button_);
  DCHECK(paste_button_);
  return {kBubbleWidth, 2 * kBubblePadding + label_->bounds().height() +
                            kButtonLabelSpacing + paste_button_->height()};
}

void ClipboardWarnBubble::SetDismissCallback(base::OnceClosure cb) {
  DCHECK(cancel_button_);
  cancel_button_->SetCallback(std::move(cb));
}

void ClipboardWarnBubble::SetProceedCallback(base::OnceClosure cb) {
  DCHECK(paste_button_);
  paste_button_->SetCallback(std::move(cb));
}

BEGIN_METADATA(ClipboardWarnBubble)
END_METADATA

}  // namespace policy