1 // Copyright 2014 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/session_crashed_bubble_view.h"
6
7 #include <stddef.h>
8
9 #include <string>
10 #include <utility>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/callback_helpers.h"
15 #include "base/macros.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/task_runner_util.h"
18 #include "build/branding_buildflags.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/metrics/metrics_reporting_state.h"
21 #include "chrome/browser/prefs/session_startup_pref.h"
22 #include "chrome/browser/sessions/session_restore.h"
23 #include "chrome/browser/ui/browser_dialogs.h"
24 #include "chrome/browser/ui/browser_list.h"
25 #include "chrome/browser/ui/browser_list_observer.h"
26 #include "chrome/browser/ui/bubble_anchor_util.h"
27 #include "chrome/browser/ui/tabs/tab_strip_model.h"
28 #include "chrome/browser/ui/views/chrome_layout_provider.h"
29 #include "chrome/browser/ui/views/chrome_typography.h"
30 #include "chrome/browser/ui/views/frame/app_menu_button.h"
31 #include "chrome/browser/ui/views/frame/browser_view.h"
32 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
33 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
34 #include "chrome/grit/chromium_strings.h"
35 #include "chrome/grit/generated_resources.h"
36 #include "chrome/installer/util/google_update_settings.h"
37 #include "components/strings/grit/components_chromium_strings.h"
38 #include "components/strings/grit/components_strings.h"
39 #include "content/public/browser/browser_context.h"
40 #include "content/public/browser/browser_thread.h"
41 #include "ui/base/buildflags.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/models/dialog_model.h"
44 #include "ui/base/window_open_disposition.h"
45 #include "ui/views/bubble/bubble_dialog_model_host.h"
46 #include "ui/views/controls/button/checkbox.h"
47 #include "ui/views/controls/button/label_button_border.h"
48 #include "ui/views/controls/button/menu_button.h"
49 #include "ui/views/controls/label.h"
50 #include "ui/views/controls/separator.h"
51 #include "ui/views/controls/styled_label.h"
52 #include "ui/views/layout/box_layout.h"
53 #include "ui/views/widget/widget.h"
54
55 namespace {
56
57 enum SessionCrashedBubbleHistogramValue {
58 SESSION_CRASHED_BUBBLE_SHOWN,
59 SESSION_CRASHED_BUBBLE_ERROR,
60 SESSION_CRASHED_BUBBLE_RESTORED,
61 SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN,
62 SESSION_CRASHED_BUBBLE_UMA_OPTIN,
63 SESSION_CRASHED_BUBBLE_HELP,
64 SESSION_CRASHED_BUBBLE_IGNORED,
65 SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN,
66 SESSION_CRASHED_BUBBLE_STARTUP_PAGES,
67 SESSION_CRASHED_BUBBLE_MAX,
68 };
69
RecordBubbleHistogramValue(SessionCrashedBubbleHistogramValue value)70 void RecordBubbleHistogramValue(SessionCrashedBubbleHistogramValue value) {
71 UMA_HISTOGRAM_ENUMERATION(
72 "SessionCrashed.Bubble", value, SESSION_CRASHED_BUBBLE_MAX);
73 }
74
DoesSupportConsentCheck()75 bool DoesSupportConsentCheck() {
76 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
77 return true;
78 #else
79 return false;
80 #endif
81 }
82
OpenUmaLink(Browser * browser,const ui::Event & event)83 void OpenUmaLink(Browser* browser, const ui::Event& event) {
84 browser->OpenURL(content::OpenURLParams(
85 GURL("https://support.google.com/chrome/answer/96817"),
86 content::Referrer(),
87 ui::DispositionFromEventFlags(event.flags(),
88 WindowOpenDisposition::NEW_FOREGROUND_TAB),
89 ui::PAGE_TRANSITION_LINK, false));
90 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_HELP);
91 }
92
93 constexpr int kUmaConsentCheckboxId = 1;
94
95 class SessionCrashedBubbleDelegate : public ui::DialogModelDelegate {
96 public:
OpenStartupPages(Browser * browser)97 void OpenStartupPages(Browser* browser) {
98 ignored_ = false;
99
100 MaybeEnableUma();
101 dialog_model()->host()->Close();
102
103 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_STARTUP_PAGES);
104 // Opening tabs has side effects, so it's preferable to do it after the
105 // bubble was closed.
106 SessionRestore::OpenStartupPagesAfterCrash(browser);
107 }
108
OnWindowClosing()109 void OnWindowClosing() {
110 if (ignored_)
111 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_IGNORED);
112 }
113
RestorePreviousSession(Browser * browser)114 void RestorePreviousSession(Browser* browser) {
115 ignored_ = false;
116 MaybeEnableUma();
117 dialog_model()->host()->Close();
118
119 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_RESTORED);
120 // Restoring tabs has side effects, so it's preferable to do it after the
121 // bubble was closed.
122 SessionRestore::RestoreSessionAfterCrash(browser);
123 }
124
MaybeEnableUma()125 void MaybeEnableUma() {
126 // Record user's choice for opt-in in to UMA.
127 // There's no opt-out choice in the crash restore bubble.
128 if (!dialog_model()->HasField(kUmaConsentCheckboxId))
129 return;
130
131 if (dialog_model()
132 ->GetCheckboxByUniqueId(kUmaConsentCheckboxId)
133 ->is_checked()) {
134 ChangeMetricsReportingState(true);
135 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_UMA_OPTIN);
136 }
137 }
138
139 private:
140 bool ignored_ = true;
141 };
142
143 } // namespace
144
145 // A helper class that listens to browser removal event.
146 class SessionCrashedBubbleView::BrowserRemovalObserver
147 : public BrowserListObserver {
148 public:
BrowserRemovalObserver(Browser * browser)149 explicit BrowserRemovalObserver(Browser* browser) : browser_(browser) {
150 DCHECK(browser_);
151 BrowserList::AddObserver(this);
152 }
153
~BrowserRemovalObserver()154 ~BrowserRemovalObserver() override { BrowserList::RemoveObserver(this); }
155
156 // Overridden from BrowserListObserver.
OnBrowserRemoved(Browser * browser)157 void OnBrowserRemoved(Browser* browser) override {
158 if (browser == browser_)
159 browser_ = nullptr;
160 }
161
browser() const162 Browser* browser() const { return browser_; }
163
164 private:
165 Browser* browser_;
166
167 DISALLOW_COPY_AND_ASSIGN(BrowserRemovalObserver);
168 };
169
170 // static
ShowIfNotOffTheRecordProfile(Browser * browser)171 void SessionCrashedBubble::ShowIfNotOffTheRecordProfile(Browser* browser) {
172 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
173 if (browser->profile()->IsOffTheRecord())
174 return;
175
176 // Observes possible browser removal before Show is called.
177 auto browser_observer =
178 std::make_unique<SessionCrashedBubbleView::BrowserRemovalObserver>(
179 browser);
180
181 if (DoesSupportConsentCheck()) {
182 base::PostTaskAndReplyWithResult(
183 GoogleUpdateSettings::CollectStatsConsentTaskRunner(), FROM_HERE,
184 base::BindOnce(&GoogleUpdateSettings::GetCollectStatsConsent),
185 base::BindOnce(&SessionCrashedBubbleView::Show,
186 std::move(browser_observer)));
187 } else {
188 SessionCrashedBubbleView::Show(std::move(browser_observer), false);
189 }
190 }
191
192 // static
Show(std::unique_ptr<BrowserRemovalObserver> browser_observer,bool uma_opted_in_already)193 void SessionCrashedBubbleView::Show(
194 std::unique_ptr<BrowserRemovalObserver> browser_observer,
195 bool uma_opted_in_already) {
196 // Determine whether or not the UMA opt-in option should be offered. It is
197 // offered only when it is a Google chrome build, user hasn't opted in yet,
198 // and the preference is modifiable by the user.
199 bool offer_uma_optin = false;
200
201 if (DoesSupportConsentCheck() && !uma_opted_in_already)
202 offer_uma_optin = !IsMetricsReportingPolicyManaged();
203
204 Browser* browser = browser_observer->browser();
205
206 if (!browser || !browser->tab_strip_model()->GetActiveWebContents()) {
207 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
208 return;
209 }
210
211 ShowBubble(browser, uma_opted_in_already, offer_uma_optin);
212 }
213
ShowBubble(Browser * browser,bool uma_opted_in_already,bool offer_uma_optin)214 views::BubbleDialogDelegateView* SessionCrashedBubbleView::ShowBubble(
215 Browser* browser,
216 bool uma_opted_in_already,
217 bool offer_uma_optin) {
218 chrome::RecordDialogCreation(chrome::DialogIdentifier::SESSION_CRASHED);
219
220 views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)
221 ->toolbar_button_provider()
222 ->GetAppMenuButton();
223
224 auto bubble_delegate_unique =
225 std::make_unique<SessionCrashedBubbleDelegate>();
226 SessionCrashedBubbleDelegate* bubble_delegate = bubble_delegate_unique.get();
227
228 ui::DialogModel::Builder dialog_builder(std::move(bubble_delegate_unique));
229 dialog_builder
230 .SetTitle(l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE))
231 .DisableCloseOnDeactivate()
232 .SetIsAlertDialog()
233 .SetWindowClosingCallback(
234 base::BindOnce(&SessionCrashedBubbleDelegate::OnWindowClosing,
235 base::Unretained(bubble_delegate)))
236 .AddBodyText(ui::DialogModelLabel(IDS_SESSION_CRASHED_VIEW_MESSAGE));
237
238 if (offer_uma_optin) {
239 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN);
240
241 dialog_builder.AddCheckbox(
242 kUmaConsentCheckboxId,
243 ui::DialogModelLabel::CreateWithLink(
244 IDS_SESSION_CRASHED_VIEW_UMA_OPTIN,
245 ui::DialogModelLabel::Link(
246 IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT,
247 base::BindRepeating(&OpenUmaLink, browser)))
248 .set_is_secondary());
249 }
250
251 const SessionStartupPref session_startup_pref =
252 SessionStartupPref::GetStartupPref(browser->profile());
253
254 if (session_startup_pref.type == SessionStartupPref::URLS &&
255 !session_startup_pref.urls.empty()) {
256 dialog_builder.AddCancelButton(
257 base::BindOnce(&SessionCrashedBubbleDelegate::OpenStartupPages,
258 base::Unretained(bubble_delegate), browser));
259 }
260
261 dialog_builder.AddOkButton(
262 base::BindOnce(&SessionCrashedBubbleDelegate::RestorePreviousSession,
263 base::Unretained(bubble_delegate), browser),
264 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON));
265
266 auto bubble = std::make_unique<views::BubbleDialogModelHost>(
267 dialog_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT);
268
269 views::BubbleDialogDelegateView* bubble_ptr = bubble.get();
270 views::BubbleDialogDelegateView::CreateBubble(bubble.release())->Show();
271
272 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_SHOWN);
273 if (uma_opted_in_already)
274 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN);
275 return bubble_ptr;
276 }
277