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