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.
5 #include "chrome/browser/ui/views/session_crashed_bubble_view.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/metrics/histogram.h"
14 #include "base/prefs/pref_service.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/metrics/metrics_reporting_state.h"
18 #include "chrome/browser/sessions/session_restore.h"
19 #include "chrome/browser/ui/browser_list.h"
20 #include "chrome/browser/ui/browser_list_observer.h"
21 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/views/frame/browser_view.h"
24 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
25 #include "chrome/browser/ui/views/toolbar/wrench_toolbar_button.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/common/url_constants.h"
29 #include "chrome/grit/chromium_strings.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "chrome/grit/google_chrome_strings.h"
32 #include "chrome/installer/util/google_update_settings.h"
33 #include "content/public/browser/browser_context.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_source.h"
36 #include "content/public/browser/web_contents.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/views/bubble/bubble_frame_view.h"
39 #include "ui/views/controls/button/checkbox.h"
40 #include "ui/views/controls/button/label_button.h"
41 #include "ui/views/controls/label.h"
42 #include "ui/views/controls/separator.h"
43 #include "ui/views/controls/styled_label.h"
44 #include "ui/views/layout/grid_layout.h"
45 #include "ui/views/layout/layout_constants.h"
46 #include "ui/views/widget/widget.h"
48 using views::GridLayout
;
52 // Fixed width of the column holding the description label of the bubble.
53 const int kWidthOfDescriptionText
= 320;
55 // Distance between checkbox and the text to the right of it.
56 const int kCheckboxTextDistance
= 4;
58 // The color of the text and background of the sub panel to offer UMA opt-in.
59 // These values match the BookmarkSyncPromoView colors.
60 const SkColor kBackgroundColor
= SkColorSetRGB(245, 245, 245);
61 const SkColor kTextColor
= SkColorSetRGB(102, 102, 102);
63 #if !defined(OS_CHROMEOS)
64 // The Finch study name and group name that enables session crashed bubble UI.
65 const char kEnableBubbleUIFinchName
[] = "EnableSessionCrashedBubbleUI";
66 const char kEnableBubbleUIGroupDisabled
[] = "Disabled";
69 enum SessionCrashedBubbleHistogramValue
{
70 SESSION_CRASHED_BUBBLE_SHOWN
,
71 SESSION_CRASHED_BUBBLE_ERROR
,
72 SESSION_CRASHED_BUBBLE_RESTORED
,
73 SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN
,
74 SESSION_CRASHED_BUBBLE_UMA_OPTIN
,
75 SESSION_CRASHED_BUBBLE_HELP
,
76 SESSION_CRASHED_BUBBLE_IGNORED
,
77 SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN
,
78 SESSION_CRASHED_BUBBLE_MAX
,
81 void RecordBubbleHistogramValue(SessionCrashedBubbleHistogramValue value
) {
82 UMA_HISTOGRAM_ENUMERATION(
83 "SessionCrashed.Bubble", value
, SESSION_CRASHED_BUBBLE_MAX
);
86 // Whether or not the bubble UI should be used.
87 bool IsBubbleUIEnabled() {
88 // Function InitiateMetricsReportingChange (called when the user chooses to
89 // opt-in to UMA) does not support Chrome OS yet, so don't show the bubble on
91 #if defined(OS_CHROMEOS)
94 const base::CommandLine
& command_line
=
95 *base::CommandLine::ForCurrentProcess();
96 if (command_line
.HasSwitch(switches::kDisableSessionCrashedBubble
))
98 if (command_line
.HasSwitch(switches::kEnableSessionCrashedBubble
))
100 const std::string group_name
= base::FieldTrialList::FindFullName(
101 kEnableBubbleUIFinchName
);
102 return group_name
!= kEnableBubbleUIGroupDisabled
;
108 // A helper class that listens to browser removal event.
109 class SessionCrashedBubbleView::BrowserRemovalObserver
110 : public chrome::BrowserListObserver
{
112 explicit BrowserRemovalObserver(Browser
* browser
) : browser_(browser
) {
114 BrowserList::AddObserver(this);
117 ~BrowserRemovalObserver() override
{ BrowserList::RemoveObserver(this); }
119 // Overridden from chrome::BrowserListObserver.
120 void OnBrowserRemoved(Browser
* browser
) override
{
121 if (browser
== browser_
)
125 Browser
* browser() const { return browser_
; }
130 DISALLOW_COPY_AND_ASSIGN(BrowserRemovalObserver
);
134 bool SessionCrashedBubbleView::Show(Browser
* browser
) {
135 if (!IsBubbleUIEnabled())
138 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
139 if (browser
->profile()->IsOffTheRecord())
142 // Observes browser removal event and will be deallocated in ShowForReal.
143 scoped_ptr
<BrowserRemovalObserver
> browser_observer(
144 new BrowserRemovalObserver(browser
));
146 // Stats collection only applies to Google Chrome builds.
147 #if defined(GOOGLE_CHROME_BUILD)
148 // Schedule a task to run GoogleUpdateSettings::GetCollectStatsConsent() on
149 // FILE thread, since it does IO. Then, call
150 // SessionCrashedBubbleView::ShowForReal with the result.
151 content::BrowserThread::PostTaskAndReplyWithResult(
152 content::BrowserThread::FILE,
154 base::Bind(&GoogleUpdateSettings::GetCollectStatsConsent
),
155 base::Bind(&SessionCrashedBubbleView::ShowForReal
,
156 base::Passed(&browser_observer
)));
158 SessionCrashedBubbleView::ShowForReal(browser_observer
.Pass(), false);
159 #endif // defined(GOOGLE_CHROME_BUILD)
165 void SessionCrashedBubbleView::ShowForReal(
166 scoped_ptr
<BrowserRemovalObserver
> browser_observer
,
167 bool uma_opted_in_already
) {
168 // Determine whether or not the UMA opt-in option should be offered. It is
169 // offered only when it is a Google chrome build, user hasn't opted in yet,
170 // and the preference is modifiable by the user.
171 bool offer_uma_optin
= false;
173 #if defined(GOOGLE_CHROME_BUILD)
174 if (!uma_opted_in_already
) {
175 offer_uma_optin
= g_browser_process
->local_state()->FindPreference(
176 prefs::kMetricsReportingEnabled
)->IsUserModifiable();
178 #endif // defined(GOOGLE_CHROME_BUILD)
180 Browser
* browser
= browser_observer
->browser();
183 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR
);
187 views::View
* anchor_view
=
188 BrowserView::GetBrowserViewForBrowser(browser
)->toolbar()->app_menu();
189 content::WebContents
* web_contents
=
190 browser
->tab_strip_model()->GetActiveWebContents();
193 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR
);
197 SessionCrashedBubbleView
* crash_bubble
=
198 new SessionCrashedBubbleView(anchor_view
, browser
, web_contents
,
200 views::BubbleDelegateView::CreateBubble(crash_bubble
)->Show();
202 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_SHOWN
);
203 if (uma_opted_in_already
)
204 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN
);
207 SessionCrashedBubbleView::SessionCrashedBubbleView(
208 views::View
* anchor_view
,
210 content::WebContents
* web_contents
,
211 bool offer_uma_optin
)
212 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
213 content::WebContentsObserver(web_contents
),
215 web_contents_(web_contents
),
216 restore_button_(NULL
),
218 offer_uma_optin_(offer_uma_optin
),
219 started_navigation_(false),
221 set_close_on_deactivate(false);
224 chrome::NOTIFICATION_TAB_CLOSING
,
225 content::Source
<content::NavigationController
>(&(
226 web_contents
->GetController())));
227 browser
->tab_strip_model()->AddObserver(this);
230 SessionCrashedBubbleView::~SessionCrashedBubbleView() {
231 browser_
->tab_strip_model()->RemoveObserver(this);
234 views::View
* SessionCrashedBubbleView::GetInitiallyFocusedView() {
235 return restore_button_
;
238 base::string16
SessionCrashedBubbleView::GetWindowTitle() const {
239 return l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE
);
242 bool SessionCrashedBubbleView::ShouldShowWindowTitle() const {
246 bool SessionCrashedBubbleView::ShouldShowCloseButton() const {
250 void SessionCrashedBubbleView::OnWidgetDestroying(views::Widget
* widget
) {
252 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_IGNORED
);
253 BubbleDelegateView::OnWidgetDestroying(widget
);
256 void SessionCrashedBubbleView::Init() {
257 // Description text label.
258 views::Label
* text_label
= new views::Label(
259 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE
));
260 text_label
->SetMultiLine(true);
261 text_label
->SetLineHeight(20);
262 text_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
263 text_label
->SizeToFit(kWidthOfDescriptionText
);
266 restore_button_
= new views::LabelButton(
267 this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON
));
268 restore_button_
->SetStyle(views::Button::STYLE_BUTTON
);
269 restore_button_
->SetIsDefault(true);
271 GridLayout
* layout
= new GridLayout(this);
272 SetLayoutManager(layout
);
275 const int kTextColumnSetId
= 0;
276 views::ColumnSet
* cs
= layout
->AddColumnSet(kTextColumnSetId
);
277 cs
->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
278 cs
->AddColumn(GridLayout::FILL
, GridLayout::FILL
, 1,
279 GridLayout::FIXED
, kWidthOfDescriptionText
, 0);
280 cs
->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
282 // Restore button row.
283 const int kButtonColumnSetId
= 1;
284 cs
= layout
->AddColumnSet(kButtonColumnSetId
);
285 cs
->AddColumn(GridLayout::TRAILING
, GridLayout::CENTER
, 1,
286 GridLayout::USE_PREF
, 0, 0);
287 cs
->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
289 layout
->StartRow(0, kTextColumnSetId
);
290 layout
->AddView(text_label
);
291 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
293 layout
->StartRow(0, kButtonColumnSetId
);
294 layout
->AddView(restore_button_
);
295 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
297 int bottom_margin
= 1;
299 // Metrics reporting option.
300 if (offer_uma_optin_
) {
301 const int kUMAOptionColumnSetId
= 2;
302 cs
= layout
->AddColumnSet(kUMAOptionColumnSetId
);
304 GridLayout::FILL
, GridLayout::FILL
, 1, GridLayout::USE_PREF
, 0, 0);
305 layout
->StartRow(1, kUMAOptionColumnSetId
);
306 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
));
307 layout
->StartRow(1, kUMAOptionColumnSetId
);
308 layout
->AddView(CreateUMAOptinView());
310 // Since the UMA opt-in row has a different background than the default
311 // background color of bubbles, the bottom margin has to be 0 to make sure
312 // the background extends to the bottom edge of the bubble.
315 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN
);
318 set_margins(gfx::Insets(1, 0, bottom_margin
, 0));
322 views::View
* SessionCrashedBubbleView::CreateUMAOptinView() {
323 // Checkbox for metric reporting setting.
324 // Since the text to the right of the checkbox can't be a simple string (needs
325 // a hyperlink in it), this checkbox contains an empty string as its label,
326 // and the real text will be added as a separate view.
327 uma_option_
= new views::Checkbox(base::string16());
328 uma_option_
->SetChecked(false);
330 // The text to the right of the checkbox.
332 base::string16 link_text
=
333 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT
);
334 base::string16 uma_text
= l10n_util::GetStringFUTF16(
335 IDS_SESSION_CRASHED_VIEW_UMA_OPTIN
,
338 views::StyledLabel
* uma_label
= new views::StyledLabel(uma_text
, this);
339 views::StyledLabel::RangeStyleInfo link_style
=
340 views::StyledLabel::RangeStyleInfo::CreateForLink();
341 link_style
.font_style
= gfx::Font::NORMAL
;
342 uma_label
->AddStyleRange(gfx::Range(offset
, offset
+ link_text
.length()),
344 views::StyledLabel::RangeStyleInfo uma_style
;
345 uma_style
.color
= kTextColor
;
346 gfx::Range
before_link_range(0, offset
);
347 if (!before_link_range
.is_empty())
348 uma_label
->AddStyleRange(before_link_range
, uma_style
);
349 gfx::Range
after_link_range(offset
+ link_text
.length(), uma_text
.length());
350 if (!after_link_range
.is_empty())
351 uma_label
->AddStyleRange(after_link_range
, uma_style
);
352 // Shift the text down by 1px to align with the checkbox.
353 uma_label
->SetBorder(views::Border::CreateEmptyBorder(1, 0, 0, 0));
355 // Create a view to hold the checkbox and the text.
356 views::View
* uma_view
= new views::View();
357 GridLayout
* uma_layout
= new GridLayout(uma_view
);
358 uma_view
->SetLayoutManager(uma_layout
);
360 uma_view
->set_background(
361 views::Background::CreateSolidBackground(kBackgroundColor
));
362 int inset_left
= GetBubbleFrameView()->GetTitleInsets().left();
364 // Bottom inset for UMA opt-in view in pixels.
365 const int kUMAOptinViewBottomInset
= 10;
366 uma_layout
->SetInsets(views::kRelatedControlVerticalSpacing
, inset_left
,
367 kUMAOptinViewBottomInset
, inset_left
);
369 const int kReportColumnSetId
= 0;
370 views::ColumnSet
* cs
= uma_layout
->AddColumnSet(kReportColumnSetId
);
371 cs
->AddColumn(GridLayout::CENTER
, GridLayout::LEADING
, 0,
372 GridLayout::USE_PREF
, 0, 0);
373 cs
->AddPaddingColumn(0, kCheckboxTextDistance
);
374 cs
->AddColumn(GridLayout::FILL
, GridLayout::FILL
, 0,
375 GridLayout::FIXED
, kWidthOfDescriptionText
, 0);
377 uma_layout
->StartRow(0, kReportColumnSetId
);
378 uma_layout
->AddView(uma_option_
);
379 uma_layout
->AddView(uma_label
);
384 void SessionCrashedBubbleView::ButtonPressed(views::Button
* sender
,
385 const ui::Event
& event
) {
386 DCHECK_EQ(sender
, restore_button_
);
387 RestorePreviousSession(sender
);
390 void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range
& range
,
392 browser_
->OpenURL(content::OpenURLParams(
393 GURL("https://support.google.com/chrome/answer/96817"),
396 ui::PAGE_TRANSITION_LINK
,
398 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_HELP
);
401 void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
403 content::NavigationController::ReloadType reload_type
) {
404 started_navigation_
= true;
407 void SessionCrashedBubbleView::DidFinishLoad(
408 content::RenderFrameHost
* render_frame_host
,
409 const GURL
& validated_url
) {
410 if (started_navigation_
)
414 void SessionCrashedBubbleView::WasShown() {
418 void SessionCrashedBubbleView::WasHidden() {
422 void SessionCrashedBubbleView::Observe(
424 const content::NotificationSource
& source
,
425 const content::NotificationDetails
& details
) {
426 if (type
== chrome::NOTIFICATION_TAB_CLOSING
)
430 void SessionCrashedBubbleView::TabDetachedAt(content::WebContents
* contents
,
432 if (web_contents_
== contents
)
436 void SessionCrashedBubbleView::RestorePreviousSession(views::Button
* sender
) {
437 SessionRestore::RestoreSessionAfterCrash(browser_
);
438 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_RESTORED
);
441 // Record user's choice for opt-in in to UMA.
442 // There's no opt-out choice in the crash restore bubble.
443 if (uma_option_
&& uma_option_
->checked()) {
444 InitiateMetricsReportingChange(true, OnMetricsReportingCallbackType());
445 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_UMA_OPTIN
);
450 void SessionCrashedBubbleView::CloseBubble() {
451 GetWidget()->Close();