Infobar material design refresh: layout
[chromium-blink-merge.git] / chrome / browser / ui / views / session_crashed_bubble_view.cc
blob5d166929c67251233cf7928f05416b977c80c626
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"
7 #include <string>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/command_line.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/histogram.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/strings/string_util.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/metrics/metrics_reporting_state.h"
20 #include "chrome/browser/sessions/session_restore.h"
21 #include "chrome/browser/ui/browser_list.h"
22 #include "chrome/browser/ui/browser_list_observer.h"
23 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/browser/ui/views/frame/browser_view.h"
26 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
27 #include "chrome/browser/ui/views/toolbar/wrench_toolbar_button.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/pref_names.h"
30 #include "chrome/common/url_constants.h"
31 #include "chrome/grit/chromium_strings.h"
32 #include "chrome/grit/generated_resources.h"
33 #include "chrome/installer/util/google_update_settings.h"
34 #include "content/public/browser/browser_context.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_source.h"
37 #include "content/public/browser/web_contents.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/views/bubble/bubble_frame_view.h"
40 #include "ui/views/controls/button/checkbox.h"
41 #include "ui/views/controls/button/label_button.h"
42 #include "ui/views/controls/label.h"
43 #include "ui/views/controls/separator.h"
44 #include "ui/views/controls/styled_label.h"
45 #include "ui/views/layout/grid_layout.h"
46 #include "ui/views/layout/layout_constants.h"
47 #include "ui/views/widget/widget.h"
49 using views::GridLayout;
51 namespace {
53 // Fixed width of the column holding the description label of the bubble.
54 const int kWidthOfDescriptionText = 320;
56 // Distance between checkbox and the text to the right of it.
57 const int kCheckboxTextDistance = 4;
59 // The color of the text and background of the sub panel to offer UMA opt-in.
60 // These values match the BookmarkSyncPromoView colors.
61 const SkColor kBackgroundColor = SkColorSetRGB(245, 245, 245);
62 const SkColor kTextColor = SkColorSetRGB(102, 102, 102);
64 #if !defined(OS_CHROMEOS)
65 // The Finch study name and group name that enables session crashed bubble UI.
66 const char kEnableBubbleUIFinchName[] = "EnableSessionCrashedBubbleUI";
67 const char kDisableBubbleUIGroupPrefix[] = "Disabled";
68 #endif
70 enum SessionCrashedBubbleHistogramValue {
71 SESSION_CRASHED_BUBBLE_SHOWN,
72 SESSION_CRASHED_BUBBLE_ERROR,
73 SESSION_CRASHED_BUBBLE_RESTORED,
74 SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN,
75 SESSION_CRASHED_BUBBLE_UMA_OPTIN,
76 SESSION_CRASHED_BUBBLE_HELP,
77 SESSION_CRASHED_BUBBLE_IGNORED,
78 SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN,
79 SESSION_CRASHED_BUBBLE_MAX,
82 void RecordBubbleHistogramValue(SessionCrashedBubbleHistogramValue value) {
83 UMA_HISTOGRAM_ENUMERATION(
84 "SessionCrashed.Bubble", value, SESSION_CRASHED_BUBBLE_MAX);
87 // Whether or not the bubble UI should be used.
88 bool IsBubbleUIEnabled() {
89 // Function InitiateMetricsReportingChange (called when the user chooses to
90 // opt-in to UMA) does not support Chrome OS yet, so don't show the bubble on
91 // Chrome OS.
92 #if defined(OS_CHROMEOS)
93 return false;
94 #else
95 const base::CommandLine& command_line =
96 *base::CommandLine::ForCurrentProcess();
97 if (command_line.HasSwitch(switches::kDisableSessionCrashedBubble))
98 return false;
99 if (command_line.HasSwitch(switches::kEnableSessionCrashedBubble))
100 return true;
101 const std::string group_name = base::FieldTrialList::FindFullName(
102 kEnableBubbleUIFinchName);
104 // When |group_name| starts with |kDisableBubbleUIGroupPrefix|, disable the
105 // bubble UI. I.e. the default behavior is bubble enabled unless overridden.
106 // This is to accommodate potential new group names without needing to change
107 // the code here.
108 return !base::StartsWith(group_name, kDisableBubbleUIGroupPrefix,
109 base::CompareCase::SENSITIVE);
110 #endif
113 } // namespace
115 // A helper class that listens to browser removal event.
116 class SessionCrashedBubbleView::BrowserRemovalObserver
117 : public chrome::BrowserListObserver {
118 public:
119 explicit BrowserRemovalObserver(Browser* browser) : browser_(browser) {
120 DCHECK(browser_);
121 BrowserList::AddObserver(this);
124 ~BrowserRemovalObserver() override { BrowserList::RemoveObserver(this); }
126 // Overridden from chrome::BrowserListObserver.
127 void OnBrowserRemoved(Browser* browser) override {
128 if (browser == browser_)
129 browser_ = NULL;
132 Browser* browser() const { return browser_; }
134 private:
135 Browser* browser_;
137 DISALLOW_COPY_AND_ASSIGN(BrowserRemovalObserver);
140 // static
141 bool SessionCrashedBubbleView::Show(Browser* browser) {
142 if (!IsBubbleUIEnabled())
143 return false;
145 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
146 if (browser->profile()->IsOffTheRecord())
147 return true;
149 // Observes browser removal event and will be deallocated in ShowForReal.
150 scoped_ptr<BrowserRemovalObserver> browser_observer(
151 new BrowserRemovalObserver(browser));
153 // Stats collection only applies to Google Chrome builds.
154 #if defined(GOOGLE_CHROME_BUILD)
155 // Schedule a task to run GoogleUpdateSettings::GetCollectStatsConsent() on
156 // FILE thread, since it does IO. Then, call
157 // SessionCrashedBubbleView::ShowForReal with the result.
158 content::BrowserThread::PostTaskAndReplyWithResult(
159 content::BrowserThread::FILE,
160 FROM_HERE,
161 base::Bind(&GoogleUpdateSettings::GetCollectStatsConsent),
162 base::Bind(&SessionCrashedBubbleView::ShowForReal,
163 base::Passed(&browser_observer)));
164 #else
165 SessionCrashedBubbleView::ShowForReal(browser_observer.Pass(), false);
166 #endif // defined(GOOGLE_CHROME_BUILD)
168 return true;
171 // static
172 void SessionCrashedBubbleView::ShowForReal(
173 scoped_ptr<BrowserRemovalObserver> browser_observer,
174 bool uma_opted_in_already) {
175 // Determine whether or not the UMA opt-in option should be offered. It is
176 // offered only when it is a Google chrome build, user hasn't opted in yet,
177 // and the preference is modifiable by the user.
178 bool offer_uma_optin = false;
180 #if defined(GOOGLE_CHROME_BUILD)
181 if (!uma_opted_in_already) {
182 offer_uma_optin = g_browser_process->local_state()->FindPreference(
183 prefs::kMetricsReportingEnabled)->IsUserModifiable();
185 #endif // defined(GOOGLE_CHROME_BUILD)
187 Browser* browser = browser_observer->browser();
189 if (!browser) {
190 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
191 return;
194 views::View* anchor_view =
195 BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu();
196 content::WebContents* web_contents =
197 browser->tab_strip_model()->GetActiveWebContents();
199 if (!web_contents) {
200 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
201 return;
204 SessionCrashedBubbleView* crash_bubble =
205 new SessionCrashedBubbleView(anchor_view, browser, web_contents,
206 offer_uma_optin);
207 views::BubbleDelegateView::CreateBubble(crash_bubble)->Show();
209 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_SHOWN);
210 if (uma_opted_in_already)
211 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN);
214 SessionCrashedBubbleView::SessionCrashedBubbleView(
215 views::View* anchor_view,
216 Browser* browser,
217 content::WebContents* web_contents,
218 bool offer_uma_optin)
219 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
220 content::WebContentsObserver(web_contents),
221 browser_(browser),
222 web_contents_(web_contents),
223 restore_button_(NULL),
224 uma_option_(NULL),
225 offer_uma_optin_(offer_uma_optin),
226 started_navigation_(false),
227 restored_(false) {
228 set_close_on_deactivate(false);
229 registrar_.Add(
230 this,
231 chrome::NOTIFICATION_TAB_CLOSING,
232 content::Source<content::NavigationController>(&(
233 web_contents->GetController())));
234 browser->tab_strip_model()->AddObserver(this);
237 SessionCrashedBubbleView::~SessionCrashedBubbleView() {
238 browser_->tab_strip_model()->RemoveObserver(this);
241 views::View* SessionCrashedBubbleView::GetInitiallyFocusedView() {
242 return restore_button_;
245 base::string16 SessionCrashedBubbleView::GetWindowTitle() const {
246 return l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE);
249 bool SessionCrashedBubbleView::ShouldShowWindowTitle() const {
250 return true;
253 bool SessionCrashedBubbleView::ShouldShowCloseButton() const {
254 return true;
257 void SessionCrashedBubbleView::OnWidgetDestroying(views::Widget* widget) {
258 if (!restored_)
259 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_IGNORED);
260 BubbleDelegateView::OnWidgetDestroying(widget);
263 void SessionCrashedBubbleView::Init() {
264 // Description text label.
265 views::Label* text_label = new views::Label(
266 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE));
267 text_label->SetMultiLine(true);
268 text_label->SetLineHeight(20);
269 text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
270 text_label->SizeToFit(kWidthOfDescriptionText);
272 // Restore button.
273 restore_button_ = new views::LabelButton(
274 this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON));
275 restore_button_->SetStyle(views::Button::STYLE_BUTTON);
276 restore_button_->SetIsDefault(true);
278 GridLayout* layout = new GridLayout(this);
279 SetLayoutManager(layout);
281 // Text row.
282 const int kTextColumnSetId = 0;
283 views::ColumnSet* cs = layout->AddColumnSet(kTextColumnSetId);
284 cs->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
285 cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
286 GridLayout::FIXED, kWidthOfDescriptionText, 0);
287 cs->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
289 // Restore button row.
290 const int kButtonColumnSetId = 1;
291 cs = layout->AddColumnSet(kButtonColumnSetId);
292 cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 1,
293 GridLayout::USE_PREF, 0, 0);
294 cs->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
296 layout->StartRow(0, kTextColumnSetId);
297 layout->AddView(text_label);
298 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
300 layout->StartRow(0, kButtonColumnSetId);
301 layout->AddView(restore_button_);
302 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
304 int bottom_margin = 1;
306 // Metrics reporting option.
307 if (offer_uma_optin_) {
308 const int kUMAOptionColumnSetId = 2;
309 cs = layout->AddColumnSet(kUMAOptionColumnSetId);
310 cs->AddColumn(
311 GridLayout::FILL, GridLayout::FILL, 1, GridLayout::USE_PREF, 0, 0);
312 layout->StartRow(1, kUMAOptionColumnSetId);
313 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
314 layout->StartRow(1, kUMAOptionColumnSetId);
315 layout->AddView(CreateUMAOptinView());
317 // Since the UMA opt-in row has a different background than the default
318 // background color of bubbles, the bottom margin has to be 0 to make sure
319 // the background extends to the bottom edge of the bubble.
320 bottom_margin = 0;
322 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN);
325 set_margins(gfx::Insets(1, 0, bottom_margin, 0));
326 Layout();
329 views::View* SessionCrashedBubbleView::CreateUMAOptinView() {
330 // Checkbox for metric reporting setting.
331 // Since the text to the right of the checkbox can't be a simple string (needs
332 // a hyperlink in it), this checkbox contains an empty string as its label,
333 // and the real text will be added as a separate view.
334 uma_option_ = new views::Checkbox(base::string16());
335 uma_option_->SetChecked(false);
337 // The text to the right of the checkbox.
338 size_t offset;
339 base::string16 link_text =
340 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT);
341 base::string16 uma_text = l10n_util::GetStringFUTF16(
342 IDS_SESSION_CRASHED_VIEW_UMA_OPTIN,
343 link_text,
344 &offset);
345 views::StyledLabel* uma_label = new views::StyledLabel(uma_text, this);
346 views::StyledLabel::RangeStyleInfo link_style =
347 views::StyledLabel::RangeStyleInfo::CreateForLink();
348 link_style.font_style = gfx::Font::NORMAL;
349 uma_label->AddStyleRange(gfx::Range(offset, offset + link_text.length()),
350 link_style);
351 views::StyledLabel::RangeStyleInfo uma_style;
352 uma_style.color = kTextColor;
353 gfx::Range before_link_range(0, offset);
354 if (!before_link_range.is_empty())
355 uma_label->AddStyleRange(before_link_range, uma_style);
356 gfx::Range after_link_range(offset + link_text.length(), uma_text.length());
357 if (!after_link_range.is_empty())
358 uma_label->AddStyleRange(after_link_range, uma_style);
359 // Shift the text down by 1px to align with the checkbox.
360 uma_label->SetBorder(views::Border::CreateEmptyBorder(1, 0, 0, 0));
362 // Create a view to hold the checkbox and the text.
363 views::View* uma_view = new views::View();
364 GridLayout* uma_layout = new GridLayout(uma_view);
365 uma_view->SetLayoutManager(uma_layout);
367 uma_view->set_background(
368 views::Background::CreateSolidBackground(kBackgroundColor));
369 int inset_left = GetBubbleFrameView()->GetTitleInsets().left();
371 // Bottom inset for UMA opt-in view in pixels.
372 const int kUMAOptinViewBottomInset = 10;
373 uma_layout->SetInsets(views::kRelatedControlVerticalSpacing, inset_left,
374 kUMAOptinViewBottomInset, inset_left);
376 const int kReportColumnSetId = 0;
377 views::ColumnSet* cs = uma_layout->AddColumnSet(kReportColumnSetId);
378 cs->AddColumn(GridLayout::CENTER, GridLayout::LEADING, 0,
379 GridLayout::USE_PREF, 0, 0);
380 cs->AddPaddingColumn(0, kCheckboxTextDistance);
381 cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
382 GridLayout::FIXED, kWidthOfDescriptionText, 0);
384 uma_layout->StartRow(0, kReportColumnSetId);
385 uma_layout->AddView(uma_option_);
386 uma_layout->AddView(uma_label);
388 return uma_view;
391 void SessionCrashedBubbleView::ButtonPressed(views::Button* sender,
392 const ui::Event& event) {
393 DCHECK_EQ(sender, restore_button_);
394 RestorePreviousSession(sender);
397 void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range& range,
398 int event_flags) {
399 browser_->OpenURL(content::OpenURLParams(
400 GURL("https://support.google.com/chrome/answer/96817"),
401 content::Referrer(),
402 NEW_FOREGROUND_TAB,
403 ui::PAGE_TRANSITION_LINK,
404 false));
405 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_HELP);
408 void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
409 const GURL& url,
410 content::NavigationController::ReloadType reload_type) {
411 started_navigation_ = true;
414 void SessionCrashedBubbleView::DidFinishLoad(
415 content::RenderFrameHost* render_frame_host,
416 const GURL& validated_url) {
417 if (started_navigation_)
418 CloseBubble();
421 void SessionCrashedBubbleView::WasShown() {
422 GetWidget()->Show();
425 void SessionCrashedBubbleView::WasHidden() {
426 GetWidget()->Hide();
429 void SessionCrashedBubbleView::Observe(
430 int type,
431 const content::NotificationSource& source,
432 const content::NotificationDetails& details) {
433 if (type == chrome::NOTIFICATION_TAB_CLOSING)
434 CloseBubble();
437 void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents,
438 int index) {
439 if (web_contents_ == contents)
440 CloseBubble();
443 void SessionCrashedBubbleView::RestorePreviousSession(views::Button* sender) {
444 SessionRestore::RestoreSessionAfterCrash(browser_);
445 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_RESTORED);
446 restored_ = true;
448 // Record user's choice for opt-in in to UMA.
449 // There's no opt-out choice in the crash restore bubble.
450 if (uma_option_ && uma_option_->checked()) {
451 InitiateMetricsReportingChange(true, OnMetricsReportingCallbackType());
452 RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_UMA_OPTIN);
454 CloseBubble();
457 void SessionCrashedBubbleView::CloseBubble() {
458 GetWidget()->Close();