blob: 4b891c8d234979d8f6ac8bcc645c65e75512f039 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/ui/views/location_bar/cookie_controls_bubble_view.h"
#include <memory>
#include "base/logging.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/string16.h"
#include "chrome/browser/ui/tab_dialogs.h"
#include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/text_utils.h"
#include "ui/views/background.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/link.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
using base::UserMetricsAction;
namespace {
// Singleton instance of the cookie bubble. The cookie bubble can only be
// shown on the active browser window, so there is no case in which it will be
// shown twice at the same time.
static CookieControlsBubbleView* g_instance;
std::unique_ptr<views::TooltipIcon> CreateInfoIcon() {
auto explanation_tooltip = std::make_unique<views::TooltipIcon>(
l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_HELP));
explanation_tooltip->set_bubble_width(
ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_BUBBLE_PREFERRED_WIDTH));
explanation_tooltip->set_anchor_point_arrow(
views::BubbleBorder::Arrow::TOP_RIGHT);
return explanation_tooltip;
}
} // namespace
// static
void CookieControlsBubbleView::ShowBubble(views::View* anchor_view,
views::Button* highlighted_button,
content::WebContents* web_contents,
CookieControlsController* controller,
CookieControlsStatus status) {
DCHECK(web_contents);
if (g_instance)
return;
base::RecordAction(UserMetricsAction("CookieControls.Bubble.Opened"));
g_instance =
new CookieControlsBubbleView(anchor_view, web_contents, controller);
g_instance->SetHighlightedButton(highlighted_button);
views::Widget* bubble_widget =
views::BubbleDialogDelegateView::CreateBubble(g_instance);
controller->Update(web_contents);
bubble_widget->Show();
}
// static
CookieControlsBubbleView* CookieControlsBubbleView::GetCookieBubble() {
return g_instance;
}
void CookieControlsBubbleView::OnStatusChanged(
CookieControlsStatus new_status,
CookieControlsEnforcement new_enforcement,
int blocked_cookies) {
if (status_ == new_status) {
OnBlockedCookiesCountChanged(blocked_cookies);
return;
}
if (new_status != CookieControlsStatus::kEnabled)
intermediate_step_ = IntermediateStep::kNone;
status_ = new_status;
enforcement_ = new_enforcement;
blocked_cookies_ = blocked_cookies;
UpdateUi();
}
void CookieControlsBubbleView::OnBlockedCookiesCountChanged(
int blocked_cookies) {
// The blocked cookie count changes quite frequently, so avoid unnecessary
// UI updates if possible.
if (blocked_cookies_ == blocked_cookies)
return;
blocked_cookies_ = blocked_cookies;
GetBubbleFrameView()->UpdateWindowTitle();
}
CookieControlsBubbleView::CookieControlsBubbleView(
views::View* anchor_view,
content::WebContents* web_contents,
CookieControlsController* controller)
: LocationBarBubbleDelegateView(anchor_view, web_contents),
controller_(controller) {
controller_observer_.Add(controller);
DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
}
CookieControlsBubbleView::~CookieControlsBubbleView() = default;
void CookieControlsBubbleView::UpdateUi() {
if (status_ == CookieControlsStatus::kDisabled) {
CloseBubble();
return;
}
GetBubbleFrameView()->UpdateWindowTitle();
text_->SetVisible(false);
show_cookies_link_->SetVisible(false);
header_view_->SetVisible(false);
if (intermediate_step_ == IntermediateStep::kTurnOffButton) {
text_->SetVisible(true);
text_->SetText(
l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_NOT_WORKING_DESCRIPTION));
auto tooltip_icon = CreateInfoIcon();
tooltip_observer_.Add(tooltip_icon.get());
extra_view_ = SetExtraView(std::move(tooltip_icon));
show_cookies_link_->SetVisible(true);
} else if (status_ == CookieControlsStatus::kEnabled) {
header_view_->SetVisible(true);
header_view_->SetImage(
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_COOKIE_BLOCKING_ON_HEADER));
text_->SetVisible(true);
text_->SetText(
l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE));
auto link = std::make_unique<views::Link>(
l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_NOT_WORKING_TITLE));
link->set_callback(
base::BindRepeating(&CookieControlsBubbleView::NotWorkingLinkClicked,
base::Unretained(this)));
extra_view_ = SetExtraView(std::move(link));
blocked_cookies_.reset();
} else {
DCHECK_EQ(status_, CookieControlsStatus::kDisabledForSite);
header_view_->SetVisible(true);
header_view_->SetImage(
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_COOKIE_BLOCKING_OFF_HEADER));
if (extra_view_)
extra_view_->SetVisible(false);
}
DialogDelegate::SetButtonLabel(
ui::DIALOG_BUTTON_OK,
intermediate_step_ == IntermediateStep::kTurnOffButton
? l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_TURN_OFF_BUTTON)
: l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_TURN_ON_BUTTON));
DialogDelegate::SetButtons(
(intermediate_step_ == IntermediateStep::kTurnOffButton ||
(status_ == CookieControlsStatus::kDisabledForSite &&
enforcement_ == CookieControlsEnforcement::kNoEnforcement))
? ui::DIALOG_BUTTON_OK
: ui::DIALOG_BUTTON_NONE);
DialogDelegate::SetAcceptCallback(base::BindOnce(
&CookieControlsBubbleView::OnDialogAccepted, base::Unretained(this)));
DialogModelChanged();
Layout();
// The show_disable_cookie_blocking_ui_ state has a different title
// configuration. To avoid jumping UI, don't resize the bubble. This should be
// safe as the bubble in this state has less content than in Enabled state.
if (intermediate_step_ != IntermediateStep::kTurnOffButton)
SizeToContents();
}
void CookieControlsBubbleView::CloseBubble() {
// Widget's Close() is async, but we don't want to use cookie_bubble_ after
// this. Additionally web_contents() may have been destroyed.
g_instance = nullptr;
LocationBarBubbleDelegateView::CloseBubble();
}
void CookieControlsBubbleView::Init() {
const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
auto text = std::make_unique<views::Label>(base::string16(),
views::style::CONTEXT_LABEL,
views::style::STYLE_SECONDARY);
text->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
text->SetMultiLine(true);
text_ = AddChildView(std::move(text));
auto cookie_link = std::make_unique<views::Link>(
l10n_util::GetStringUTF16(IDS_BLOCKED_COOKIES_INFO));
cookie_link->SetMultiLine(true);
cookie_link->set_callback(
base::BindRepeating(&CookieControlsBubbleView::ShowCookiesLinkClicked,
base::Unretained(this)));
cookie_link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
show_cookies_link_ = AddChildView(std::move(cookie_link));
// TODO(crbug.com/1013092): The bubble should display a header view with full
// width without having to tweak margins.
gfx::Insets insets = margins();
set_margins(gfx::Insets(insets.top(), 0, insets.bottom(), 0));
SetBorder(views::CreateEmptyBorder(0, insets.left(), 0, insets.right()));
}
void CookieControlsBubbleView::AddedToWidget() {
auto header_view = std::make_unique<NonAccessibleImageView>();
header_view_ = header_view.get();
header_view_->SetBackground(views::CreateThemedSolidBackground(
header_view_, ui::NativeTheme::kColorId_BubbleFooterBackground));
GetBubbleFrameView()->SetHeaderView(std::move(header_view));
}
gfx::Size CookieControlsBubbleView::CalculatePreferredSize() const {
// The total width of this view should always be identical to the width
// of the header images.
int width = ui::ResourceBundle::GetSharedInstance()
.GetImageSkiaNamed(IDR_COOKIE_BLOCKING_ON_HEADER)
->width();
return gfx::Size{width, GetHeightForWidth(width)};
}
base::string16 CookieControlsBubbleView::GetWindowTitle() const {
switch (intermediate_step_) {
case IntermediateStep::kTurnOffButton:
return l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_NOT_WORKING_TITLE);
case IntermediateStep::kNone: {
// Determine title based on status_ instead.
}
}
switch (status_) {
case CookieControlsStatus::kEnabled:
return l10n_util::GetPluralStringFUTF16(IDS_COOKIE_CONTROLS_DIALOG_TITLE,
blocked_cookies_.value_or(0));
case CookieControlsStatus::kDisabledForSite:
return l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_DIALOG_TITLE_OFF);
case CookieControlsStatus::kUninitialized:
return base::string16();
case CookieControlsStatus::kDisabled:
NOTREACHED();
return base::string16();
}
}
bool CookieControlsBubbleView::ShouldShowWindowTitle() const {
return true;
}
bool CookieControlsBubbleView::ShouldShowCloseButton() const {
return true;
}
void CookieControlsBubbleView::WindowClosing() {
// |cookie_bubble_| can be a new bubble by this point (as Close(); doesn't
// call this right away). Only set to nullptr when it's this bubble.
bool this_bubble = g_instance == this;
if (this_bubble)
g_instance = nullptr;
controller_->OnUiClosing();
}
void CookieControlsBubbleView::OnDialogAccepted() {
if (intermediate_step_ == IntermediateStep::kTurnOffButton) {
controller_->OnCookieBlockingEnabledForSite(false);
} else {
DCHECK_EQ(status_, CookieControlsStatus::kDisabledForSite);
DCHECK_EQ(intermediate_step_, IntermediateStep::kNone);
controller_->OnCookieBlockingEnabledForSite(true);
}
}
void CookieControlsBubbleView::ShowCookiesLinkClicked() {
base::RecordAction(UserMetricsAction("CookieControls.Bubble.CookiesInUse"));
TabDialogs::FromWebContents(web_contents())->ShowCollectedCookies();
GetWidget()->Close();
}
void CookieControlsBubbleView::NotWorkingLinkClicked() {
DCHECK_EQ(status_, CookieControlsStatus::kEnabled);
base::RecordAction(UserMetricsAction("CookieControls.Bubble.NotWorking"));
// Don't go through the controller as this is an intermediary state that
// is only relevant for the bubble UI.
intermediate_step_ = IntermediateStep::kTurnOffButton;
UpdateUi();
}
void CookieControlsBubbleView::OnTooltipBubbleShown(views::TooltipIcon* icon) {
base::RecordAction(UserMetricsAction("CookieControls.Bubble.TooltipShown"));
}
void CookieControlsBubbleView::OnTooltipIconDestroying(
views::TooltipIcon* icon) {
tooltip_observer_.Remove(icon);
}