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"
9 #include "base/prefs/pref_service.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/sessions/session_restore.h"
13 #include "chrome/browser/ui/options/options_util.h"
14 #include "chrome/browser/ui/startup/session_crashed_bubble.h"
15 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/browser/ui/views/frame/browser_view.h"
18 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/common/url_constants.h"
21 #include "chrome/installer/util/google_update_settings.h"
22 #include "content/public/browser/browser_context.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/web_contents.h"
25 #include "grit/chromium_strings.h"
26 #include "grit/generated_resources.h"
27 #include "grit/google_chrome_strings.h"
28 #include "grit/ui_resources.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/views/controls/button/checkbox.h"
32 #include "ui/views/controls/button/label_button.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/controls/separator.h"
35 #include "ui/views/controls/styled_label.h"
36 #include "ui/views/layout/grid_layout.h"
37 #include "ui/views/layout/layout_constants.h"
38 #include "ui/views/widget/widget.h"
40 using views::GridLayout
;
44 // Fixed width of the column holding the description label of the bubble.
45 const int kWidthOfDescriptionText
= 320;
47 // Distance between checkbox and the text to the right of it.
48 const int kCheckboxTextDistance
= 4;
50 // Margins width for the top rows to compensate for the bottom panel for which
51 // we don't want any margin.
52 const int kMarginWidth
= 12;
53 const int kMarginHeight
= kMarginWidth
;
55 // The color of the background of the sub panel to offer UMA optin.
56 const SkColor kLightGrayBackgroundColor
= 0xFFF0F0F0;
57 const SkColor kWhiteBackgroundColor
= 0xFFFFFFFF;
59 bool ShouldOfferMetricsReporting() {
60 // Stats collection only applies to Google Chrome builds.
61 #if defined(GOOGLE_CHROME_BUILD)
62 // Only show metrics reporting option if user didn't already consent to it.
63 if (GoogleUpdateSettings::GetCollectStatsConsent())
65 return g_browser_process
->local_state()->FindPreference(
66 prefs::kMetricsReportingEnabled
)->IsUserModifiable();
69 #endif // defined(GOOGLE_CHROME_BUILD)
75 void SessionCrashedBubbleView::Show(Browser
* browser
) {
76 if (browser
->profile()->IsOffTheRecord())
79 views::View
* anchor_view
=
80 BrowserView::GetBrowserViewForBrowser(browser
)->toolbar()->app_menu();
81 content::WebContents
* web_contents
=
82 browser
->tab_strip_model()->GetActiveWebContents();
83 SessionCrashedBubbleView
* crash_bubble
=
84 new SessionCrashedBubbleView(anchor_view
, browser
, web_contents
);
85 views::BubbleDelegateView::CreateBubble(crash_bubble
)->Show();
88 SessionCrashedBubbleView::SessionCrashedBubbleView(
89 views::View
* anchor_view
,
91 content::WebContents
* web_contents
)
92 : BubbleDelegateView(anchor_view
, views::BubbleBorder::TOP_RIGHT
),
93 content::WebContentsObserver(web_contents
),
95 web_contents_(web_contents
),
96 restore_button_(NULL
),
99 started_navigation_(false) {
100 set_close_on_deactivate(false);
103 chrome::NOTIFICATION_TAB_CLOSING
,
104 content::Source
<content::NavigationController
>(&(
105 web_contents
->GetController())));
106 browser
->tab_strip_model()->AddObserver(this);
109 SessionCrashedBubbleView::~SessionCrashedBubbleView() {
110 browser_
->tab_strip_model()->RemoveObserver(this);
113 views::View
* SessionCrashedBubbleView::GetInitiallyFocusedView() {
114 return restore_button_
;
117 void SessionCrashedBubbleView::Init() {
118 ui::ResourceBundle
* rb
= &ui::ResourceBundle::GetSharedInstance();
121 close_
= new views::LabelButton(this, base::string16());
122 close_
->SetImage(views::CustomButton::STATE_NORMAL
,
123 *rb
->GetImageNamed(IDR_CLOSE_2
).ToImageSkia());
124 close_
->SetImage(views::CustomButton::STATE_HOVERED
,
125 *rb
->GetImageNamed(IDR_CLOSE_2_H
).ToImageSkia());
126 close_
->SetImage(views::CustomButton::STATE_PRESSED
,
127 *rb
->GetImageNamed(IDR_CLOSE_2_P
).ToImageSkia());
128 close_
->SetSize(close_
->GetPreferredSize());
129 close_
->SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0));
131 // Bubble title label.
132 views::Label
* title_label
= new views::Label(
133 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE
));
134 title_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
135 title_label
->SetFontList(rb
->GetFontList(ui::ResourceBundle::BoldFont
));
137 // Description text label.
138 views::Label
* text_label
= new views::Label(
139 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE
));
140 text_label
->SetMultiLine(true);
141 text_label
->SetLineHeight(20);
142 text_label
->SetEnabledColor(SK_ColorDKGRAY
);
143 text_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
146 restore_button_
= new views::LabelButton(
147 this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON
));
148 restore_button_
->SetStyle(views::Button::STYLE_BUTTON
);
149 restore_button_
->SetIsDefault(true);
150 restore_button_
->SetFontList(rb
->GetFontList(ui::ResourceBundle::BoldFont
));
152 GridLayout
* layout
= new GridLayout(this);
153 SetLayoutManager(layout
);
155 // Title and close button row.
156 const int kTitleColumnSetId
= 0;
157 views::ColumnSet
* cs
= layout
->AddColumnSet(kTitleColumnSetId
);
158 cs
->AddPaddingColumn(0, kMarginWidth
);
159 cs
->AddColumn(GridLayout::LEADING
, GridLayout::TRAILING
, 0,
160 GridLayout::USE_PREF
, 0, 0);
161 cs
->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing
);
162 cs
->AddColumn(GridLayout::TRAILING
, GridLayout::LEADING
, 0,
163 GridLayout::USE_PREF
, 0, 0);
166 const int kTextColumnSetId
= 1;
167 cs
= layout
->AddColumnSet(kTextColumnSetId
);
168 cs
->AddPaddingColumn(0, kMarginWidth
);
169 cs
->AddColumn(GridLayout::FILL
, GridLayout::FILL
, 0,
170 GridLayout::FIXED
, kWidthOfDescriptionText
, 0);
172 // Restore button row
173 const int kButtonColumnSetId
= 2;
174 cs
= layout
->AddColumnSet(kButtonColumnSetId
);
175 cs
->AddPaddingColumn(0, kMarginWidth
);
176 cs
->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing
);
177 cs
->AddColumn(GridLayout::TRAILING
, GridLayout::CENTER
, 0,
178 GridLayout::USE_PREF
, 0, 0);
179 cs
->AddPaddingColumn(0, kMarginWidth
);
181 layout
->AddPaddingRow(0, kMarginHeight
);
182 layout
->StartRow(0, kTitleColumnSetId
);
183 layout
->AddView(title_label
);
184 layout
->AddView(close_
);
185 layout
->AddPaddingRow(0, kMarginHeight
);
187 layout
->StartRow(0, kTextColumnSetId
);
188 layout
->AddView(text_label
);
189 layout
->AddPaddingRow(0, kMarginHeight
);
191 layout
->StartRow(0, kButtonColumnSetId
);
192 layout
->AddView(restore_button_
);
193 layout
->AddPaddingRow(0, kMarginHeight
);
195 // Metrics reporting option.
196 if (ShouldOfferMetricsReporting())
197 CreateUmaOptinView(layout
);
199 set_color(kWhiteBackgroundColor
);
200 set_margins(gfx::Insets());
204 void SessionCrashedBubbleView::CreateUmaOptinView(GridLayout
* layout
) {
205 // Checkbox for metric reporting setting.
206 // Since the text to the right of the checkbox can't be a simple string (needs
207 // a hyperlink in it), this checkbox contains an empty string as its label,
208 // and the real text will be added as a separate view.
209 uma_option_
= new views::Checkbox(base::string16());
210 uma_option_
->SetTextColor(views::Button::STATE_NORMAL
, SK_ColorGRAY
);
211 uma_option_
->SetChecked(false);
212 uma_option_
->set_background(
213 views::Background::CreateSolidBackground(kLightGrayBackgroundColor
));
214 uma_option_
->set_listener(this);
216 // The text to the right of the checkbox.
218 base::string16 link_text
=
219 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT
);
220 base::string16 uma_text
= l10n_util::GetStringFUTF16(
221 IDS_SESSION_CRASHED_VIEW_UMA_OPTIN
,
224 views::StyledLabel
* uma_label
= new views::StyledLabel(uma_text
, this);
225 uma_label
->set_background(
226 views::Background::CreateSolidBackground(kLightGrayBackgroundColor
));
227 views::StyledLabel::RangeStyleInfo link_style
=
228 views::StyledLabel::RangeStyleInfo::CreateForLink();
229 link_style
.font_style
= gfx::Font::NORMAL
;
230 uma_label
->AddStyleRange(gfx::Range(offset
, offset
+ link_text
.length()),
232 views::StyledLabel::RangeStyleInfo uma_style
;
233 uma_style
.color
= SK_ColorGRAY
;
234 gfx::Range
before_link_range(0, offset
);
235 if (!before_link_range
.is_empty())
236 uma_label
->AddStyleRange(before_link_range
, uma_style
);
237 gfx::Range
after_link_range(offset
+ link_text
.length(), uma_text
.length());
238 if (!after_link_range
.is_empty())
239 uma_label
->AddStyleRange(after_link_range
, uma_style
);
241 // We use a border instead of padding so that the background color reach
242 // the edges of the bubble.
243 uma_option_
->SetBorder(
244 views::Border::CreateSolidSidedBorder(0, kMarginWidth
, 0, 0,
245 kLightGrayBackgroundColor
));
246 uma_label
->SetBorder(
247 views::Border::CreateSolidSidedBorder(
248 kMarginHeight
, kCheckboxTextDistance
, kMarginHeight
, kMarginWidth
,
249 kLightGrayBackgroundColor
));
252 const int kSeparatorColumnSetId
= 3;
253 views::ColumnSet
* cs
= layout
->AddColumnSet(kSeparatorColumnSetId
);
254 cs
->AddColumn(GridLayout::FILL
, GridLayout::FILL
, 0,
255 GridLayout::FIXED
, kWidthOfDescriptionText
+ kMarginWidth
, 0);
258 const int kReportColumnSetId
= 4;
259 cs
= layout
->AddColumnSet(kReportColumnSetId
);
260 cs
->AddColumn(GridLayout::CENTER
, GridLayout::FILL
, 0,
261 GridLayout::USE_PREF
, 0, 0);
262 cs
->AddColumn(GridLayout::FILL
, GridLayout::FILL
, 0,
263 GridLayout::FIXED
, kWidthOfDescriptionText
, 0);
265 layout
->StartRow(0, kSeparatorColumnSetId
);
266 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
));
267 layout
->StartRow(0, kReportColumnSetId
);
268 layout
->AddView(uma_option_
);
269 layout
->AddView(uma_label
);
272 void SessionCrashedBubbleView::ButtonPressed(views::Button
* sender
,
273 const ui::Event
& event
) {
275 if (sender
== restore_button_
)
276 RestorePreviousSession(sender
);
277 else if (sender
== close_
)
281 void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range
& range
,
283 browser_
->OpenURL(content::OpenURLParams(
284 GURL("https://support.google.com/chrome/answer/96817"),
287 content::PAGE_TRANSITION_LINK
,
291 void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
293 content::NavigationController::ReloadType reload_type
) {
294 started_navigation_
= true;
297 void SessionCrashedBubbleView::DidFinishLoad(
299 const GURL
& validated_url
,
301 content::RenderViewHost
* render_view_host
) {
302 if (started_navigation_
)
306 void SessionCrashedBubbleView::WasShown() {
310 void SessionCrashedBubbleView::WasHidden() {
314 void SessionCrashedBubbleView::Observe(
316 const content::NotificationSource
& source
,
317 const content::NotificationDetails
& details
) {
318 if (type
== chrome::NOTIFICATION_TAB_CLOSING
)
322 void SessionCrashedBubbleView::TabDetachedAt(content::WebContents
* contents
,
324 if (web_contents_
== contents
)
328 void SessionCrashedBubbleView::RestorePreviousSession(views::Button
* sender
) {
329 SessionRestore::RestoreSessionAfterCrash(browser_
);
331 // Record user's choice for opting in to UMA.
332 // There's no opting-out choice in the crash restore bubble.
333 if (uma_option_
&& uma_option_
->checked()) {
334 // TODO: Clean up function ResolveMetricsReportingEnabled so that user pref
335 // is stored automatically.
336 OptionsUtil::ResolveMetricsReportingEnabled(true);
337 g_browser_process
->local_state()->SetBoolean(
338 prefs::kMetricsReportingEnabled
, true);
343 void SessionCrashedBubbleView::CloseBubble() {
344 GetWidget()->Close();
347 bool ShowSessionCrashedBubble(Browser
* browser
) {
348 SessionCrashedBubbleView::Show(browser
);