1 // Copyright 2019 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/location_bar/cookie_controls_bubble_view.h"
6
7 #include <memory>
8 #include "base/logging.h"
9 #include "base/metrics/user_metrics.h"
10 #include "base/metrics/user_metrics_action.h"
11 #include "base/strings/string16.h"
12 #include "chrome/browser/ui/tab_dialogs.h"
13 #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
14 #include "chrome/browser/ui/views/chrome_layout_provider.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "chrome/grit/theme_resources.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/base/ui_base_types.h"
20 #include "ui/gfx/text_utils.h"
21 #include "ui/views/background.h"
22 #include "ui/views/bubble/bubble_frame_view.h"
23 #include "ui/views/controls/image_view.h"
24 #include "ui/views/controls/link.h"
25 #include "ui/views/layout/box_layout.h"
26 #include "ui/views/widget/widget.h"
27
28 using base::UserMetricsAction;
29
30 namespace {
31
32 // Singleton instance of the cookie bubble. The cookie bubble can only be
33 // shown on the active browser window, so there is no case in which it will be
34 // shown twice at the same time.
35 static CookieControlsBubbleView* g_instance;
36
CreateInfoIcon()37 std::unique_ptr<views::TooltipIcon> CreateInfoIcon() {
38 auto explanation_tooltip = std::make_unique<views::TooltipIcon>(
39 l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_HELP));
40 explanation_tooltip->set_bubble_width(
41 ChromeLayoutProvider::Get()->GetDistanceMetric(
42 views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
43 explanation_tooltip->set_anchor_point_arrow(
44 views::BubbleBorder::Arrow::TOP_RIGHT);
45 return explanation_tooltip;
46 }
47
48 } // namespace
49
50 // static
ShowBubble(views::View * anchor_view,views::Button * highlighted_button,content::WebContents * web_contents,content_settings::CookieControlsController * controller,CookieControlsStatus status)51 void CookieControlsBubbleView::ShowBubble(
52 views::View* anchor_view,
53 views::Button* highlighted_button,
54 content::WebContents* web_contents,
55 content_settings::CookieControlsController* controller,
56 CookieControlsStatus status) {
57 DCHECK(web_contents);
58 if (g_instance)
59 return;
60
61 base::RecordAction(UserMetricsAction("CookieControls.Bubble.Opened"));
62 g_instance =
63 new CookieControlsBubbleView(anchor_view, web_contents, controller);
64 g_instance->SetHighlightedButton(highlighted_button);
65 views::Widget* bubble_widget =
66 views::BubbleDialogDelegateView::CreateBubble(g_instance);
67 controller->Update(web_contents);
68 bubble_widget->Show();
69 }
70
71 // static
GetCookieBubble()72 CookieControlsBubbleView* CookieControlsBubbleView::GetCookieBubble() {
73 return g_instance;
74 }
75
OnStatusChanged(CookieControlsStatus new_status,CookieControlsEnforcement new_enforcement,int allowed_cookies,int blocked_cookies)76 void CookieControlsBubbleView::OnStatusChanged(
77 CookieControlsStatus new_status,
78 CookieControlsEnforcement new_enforcement,
79 int allowed_cookies,
80 int blocked_cookies) {
81 if (status_ == new_status && enforcement_ == new_enforcement) {
82 OnCookiesCountChanged(allowed_cookies, blocked_cookies);
83 return;
84 }
85 if (new_status != CookieControlsStatus::kEnabled)
86 intermediate_step_ = IntermediateStep::kNone;
87 status_ = new_status;
88 enforcement_ = new_enforcement;
89 blocked_cookies_ = blocked_cookies;
90 UpdateUi();
91 }
92
OnCookiesCountChanged(int allowed_cookies,int blocked_cookies)93 void CookieControlsBubbleView::OnCookiesCountChanged(int allowed_cookies,
94 int blocked_cookies) {
95 // The blocked cookie count changes quite frequently, so avoid unnecessary
96 // UI updates if possible.
97 if (blocked_cookies_ == blocked_cookies)
98 return;
99
100 blocked_cookies_ = blocked_cookies;
101 GetBubbleFrameView()->UpdateWindowTitle();
102 }
103
CookieControlsBubbleView(views::View * anchor_view,content::WebContents * web_contents,content_settings::CookieControlsController * controller)104 CookieControlsBubbleView::CookieControlsBubbleView(
105 views::View* anchor_view,
106 content::WebContents* web_contents,
107 content_settings::CookieControlsController* controller)
108 : LocationBarBubbleDelegateView(anchor_view, web_contents),
109 controller_(controller) {
110 controller_observer_.Add(controller);
111 SetButtons(ui::DIALOG_BUTTON_NONE);
112 }
113
114 CookieControlsBubbleView::~CookieControlsBubbleView() = default;
115
UpdateUi()116 void CookieControlsBubbleView::UpdateUi() {
117 if (status_ == CookieControlsStatus::kDisabled) {
118 CloseBubble();
119 return;
120 }
121
122 GetBubbleFrameView()->UpdateWindowTitle();
123 text_->SetVisible(false);
124 show_cookies_link_->SetVisible(false);
125 header_view_->SetVisible(false);
126
127 if (intermediate_step_ == IntermediateStep::kTurnOffButton) {
128 text_->SetVisible(true);
129 text_->SetText(
130 l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_NOT_WORKING_DESCRIPTION));
131 auto tooltip_icon = CreateInfoIcon();
132 tooltip_observer_.Add(tooltip_icon.get());
133 extra_view_ = SetExtraView(std::move(tooltip_icon));
134 show_cookies_link_->SetVisible(true);
135 } else if (status_ == CookieControlsStatus::kEnabled) {
136 header_view_->SetVisible(true);
137 header_view_->SetImage(
138 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
139 IDR_COOKIE_BLOCKING_ON_HEADER));
140 text_->SetVisible(true);
141 text_->SetText(
142 l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE));
143 auto link = std::make_unique<views::Link>(
144 l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_NOT_WORKING_TITLE));
145 link->SetCallback(
146 base::BindRepeating(&CookieControlsBubbleView::NotWorkingLinkClicked,
147 base::Unretained(this)));
148 extra_view_ = SetExtraView(std::move(link));
149 blocked_cookies_.reset();
150 } else {
151 DCHECK_EQ(status_, CookieControlsStatus::kDisabledForSite);
152 header_view_->SetVisible(true);
153 header_view_->SetImage(
154 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
155 IDR_COOKIE_BLOCKING_OFF_HEADER));
156 if (extra_view_)
157 extra_view_->SetVisible(false);
158 }
159
160 SetButtonLabel(
161 ui::DIALOG_BUTTON_OK,
162 intermediate_step_ == IntermediateStep::kTurnOffButton
163 ? l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_TURN_OFF_BUTTON)
164 : l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_TURN_ON_BUTTON));
165 SetButtons((intermediate_step_ == IntermediateStep::kTurnOffButton ||
166 (status_ == CookieControlsStatus::kDisabledForSite &&
167 enforcement_ == CookieControlsEnforcement::kNoEnforcement))
168 ? ui::DIALOG_BUTTON_OK
169 : ui::DIALOG_BUTTON_NONE);
170 SetAcceptCallback(base::BindOnce(&CookieControlsBubbleView::OnDialogAccepted,
171 base::Unretained(this)));
172
173 DialogModelChanged();
174 Layout();
175
176 // The show_disable_cookie_blocking_ui_ state has a different title
177 // configuration. To avoid jumping UI, don't resize the bubble. This should be
178 // safe as the bubble in this state has less content than in Enabled state.
179 if (intermediate_step_ != IntermediateStep::kTurnOffButton)
180 SizeToContents();
181 }
182
CloseBubble()183 void CookieControlsBubbleView::CloseBubble() {
184 // Widget's Close() is async, but we don't want to use cookie_bubble_ after
185 // this. Additionally web_contents() may have been destroyed.
186 g_instance = nullptr;
187 LocationBarBubbleDelegateView::CloseBubble();
188 }
189
Init()190 void CookieControlsBubbleView::Init() {
191 const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
192 SetLayoutManager(std::make_unique<views::BoxLayout>(
193 views::BoxLayout::Orientation::kVertical, gfx::Insets(),
194 provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
195
196 auto text = std::make_unique<views::Label>(base::string16(),
197 views::style::CONTEXT_LABEL,
198 views::style::STYLE_SECONDARY);
199 text->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
200 text->SetMultiLine(true);
201 text_ = AddChildView(std::move(text));
202
203 auto cookie_link = std::make_unique<views::Link>(
204 l10n_util::GetStringUTF16(IDS_BLOCKED_COOKIES_INFO));
205 cookie_link->SetMultiLine(true);
206 cookie_link->SetCallback(
207 base::BindRepeating(&CookieControlsBubbleView::ShowCookiesLinkClicked,
208 base::Unretained(this)));
209 cookie_link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
210 show_cookies_link_ = AddChildView(std::move(cookie_link));
211
212 // TODO(crbug.com/1013092): The bubble should display a header view with full
213 // width without having to tweak margins.
214 gfx::Insets insets = margins();
215 set_margins(gfx::Insets(insets.top(), 0, insets.bottom(), 0));
216 SetBorder(views::CreateEmptyBorder(0, insets.left(), 0, insets.right()));
217 }
218
AddedToWidget()219 void CookieControlsBubbleView::AddedToWidget() {
220 auto header_view = std::make_unique<NonAccessibleImageView>();
221 header_view_ = header_view.get();
222 header_view_->SetBackground(views::CreateThemedSolidBackground(
223 header_view_, ui::NativeTheme::kColorId_BubbleFooterBackground));
224 GetBubbleFrameView()->SetHeaderView(std::move(header_view));
225 }
226
CalculatePreferredSize() const227 gfx::Size CookieControlsBubbleView::CalculatePreferredSize() const {
228 // The total width of this view should always be identical to the width
229 // of the header images.
230 int width = ui::ResourceBundle::GetSharedInstance()
231 .GetImageSkiaNamed(IDR_COOKIE_BLOCKING_ON_HEADER)
232 ->width();
233 return gfx::Size{width, GetHeightForWidth(width)};
234 }
235
GetWindowTitle() const236 base::string16 CookieControlsBubbleView::GetWindowTitle() const {
237 switch (intermediate_step_) {
238 case IntermediateStep::kTurnOffButton:
239 return l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_NOT_WORKING_TITLE);
240 case IntermediateStep::kNone: {
241 // Determine title based on status_ instead.
242 }
243 }
244 switch (status_) {
245 case CookieControlsStatus::kEnabled:
246 return l10n_util::GetPluralStringFUTF16(IDS_COOKIE_CONTROLS_DIALOG_TITLE,
247 blocked_cookies_.value_or(0));
248 case CookieControlsStatus::kDisabledForSite:
249 return l10n_util::GetStringUTF16(IDS_COOKIE_CONTROLS_DIALOG_TITLE_OFF);
250 case CookieControlsStatus::kUninitialized:
251 return base::string16();
252 case CookieControlsStatus::kDisabled:
253 NOTREACHED();
254 return base::string16();
255 }
256 }
257
ShouldShowWindowTitle() const258 bool CookieControlsBubbleView::ShouldShowWindowTitle() const {
259 return true;
260 }
261
ShouldShowCloseButton() const262 bool CookieControlsBubbleView::ShouldShowCloseButton() const {
263 return true;
264 }
265
WindowClosing()266 void CookieControlsBubbleView::WindowClosing() {
267 // |cookie_bubble_| can be a new bubble by this point (as Close(); doesn't
268 // call this right away). Only set to nullptr when it's this bubble.
269 bool this_bubble = g_instance == this;
270 if (this_bubble)
271 g_instance = nullptr;
272
273 controller_->OnUiClosing();
274 }
275
OnDialogAccepted()276 void CookieControlsBubbleView::OnDialogAccepted() {
277 if (intermediate_step_ == IntermediateStep::kTurnOffButton) {
278 controller_->OnCookieBlockingEnabledForSite(false);
279 } else {
280 DCHECK_EQ(status_, CookieControlsStatus::kDisabledForSite);
281 DCHECK_EQ(intermediate_step_, IntermediateStep::kNone);
282 controller_->OnCookieBlockingEnabledForSite(true);
283 }
284 }
285
ShowCookiesLinkClicked()286 void CookieControlsBubbleView::ShowCookiesLinkClicked() {
287 base::RecordAction(UserMetricsAction("CookieControls.Bubble.CookiesInUse"));
288 TabDialogs::FromWebContents(web_contents())->ShowCollectedCookies();
289 GetWidget()->Close();
290 }
291
NotWorkingLinkClicked()292 void CookieControlsBubbleView::NotWorkingLinkClicked() {
293 DCHECK_EQ(status_, CookieControlsStatus::kEnabled);
294 base::RecordAction(UserMetricsAction("CookieControls.Bubble.NotWorking"));
295 // Don't go through the controller as this is an intermediary state that
296 // is only relevant for the bubble UI.
297 intermediate_step_ = IntermediateStep::kTurnOffButton;
298 UpdateUi();
299 }
300
OnTooltipBubbleShown(views::TooltipIcon * icon)301 void CookieControlsBubbleView::OnTooltipBubbleShown(views::TooltipIcon* icon) {
302 base::RecordAction(UserMetricsAction("CookieControls.Bubble.TooltipShown"));
303 }
304
OnTooltipIconDestroying(views::TooltipIcon * icon)305 void CookieControlsBubbleView::OnTooltipIconDestroying(
306 views::TooltipIcon* icon) {
307 tooltip_observer_.Remove(icon);
308 }
309