session_crashed_bubble_view.cc revision 0de6073388f4e2780db8536178b129cd8f6ab386
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 <vector>
8
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"
39
40using views::GridLayout;
41
42namespace {
43
44// Fixed width of the column holding the description label of the bubble.
45const int kWidthOfDescriptionText = 320;
46
47// Distance between checkbox and the text to the right of it.
48const int kCheckboxTextDistance = 4;
49
50// Margins width for the top rows to compensate for the bottom panel for which
51// we don't want any margin.
52const int kMarginWidth = 12;
53const int kMarginHeight = kMarginWidth;
54
55// The color of the background of the sub panel to offer UMA optin.
56const SkColor kLightGrayBackgroundColor = 0xFFF0F0F0;
57const SkColor kWhiteBackgroundColor = 0xFFFFFFFF;
58
59bool 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())
64    return false;
65  return g_browser_process->local_state()->FindPreference(
66      prefs::kMetricsReportingEnabled)->IsUserModifiable();
67#else
68  return false;
69#endif  // defined(GOOGLE_CHROME_BUILD)
70}
71
72}  // namespace
73
74// static
75void SessionCrashedBubbleView::Show(Browser* browser) {
76  if (browser->profile()->IsOffTheRecord())
77    return;
78
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();
86}
87
88SessionCrashedBubbleView::SessionCrashedBubbleView(
89    views::View* anchor_view,
90    Browser* browser,
91    content::WebContents* web_contents)
92    : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
93      content::WebContentsObserver(web_contents),
94      browser_(browser),
95      web_contents_(web_contents),
96      restore_button_(NULL),
97      close_(NULL),
98      uma_option_(NULL),
99      started_navigation_(false) {
100  set_close_on_deactivate(false);
101  registrar_.Add(
102      this,
103      chrome::NOTIFICATION_TAB_CLOSING,
104      content::Source<content::NavigationController>(&(
105          web_contents->GetController())));
106  browser->tab_strip_model()->AddObserver(this);
107}
108
109SessionCrashedBubbleView::~SessionCrashedBubbleView() {
110  browser_->tab_strip_model()->RemoveObserver(this);
111}
112
113views::View* SessionCrashedBubbleView::GetInitiallyFocusedView() {
114  return restore_button_;
115}
116
117void SessionCrashedBubbleView::Init() {
118  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
119
120  // Close button.
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));
130
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));
136
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);
144
145  // Restore button.
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));
151
152  GridLayout* layout = new GridLayout(this);
153  SetLayoutManager(layout);
154
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);
164
165  // Text row.
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);
171
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);
180
181  layout->AddPaddingRow(0, kMarginHeight);
182  layout->StartRow(0, kTitleColumnSetId);
183  layout->AddView(title_label);
184  layout->AddView(close_);
185  layout->AddPaddingRow(0, kMarginHeight);
186
187  layout->StartRow(0, kTextColumnSetId);
188  layout->AddView(text_label);
189  layout->AddPaddingRow(0, kMarginHeight);
190
191  layout->StartRow(0, kButtonColumnSetId);
192  layout->AddView(restore_button_);
193  layout->AddPaddingRow(0, kMarginHeight);
194
195  // Metrics reporting option.
196  if (ShouldOfferMetricsReporting())
197    CreateUmaOptinView(layout);
198
199  set_color(kWhiteBackgroundColor);
200  set_margins(gfx::Insets());
201  Layout();
202}
203
204void 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);
215
216  // The text to the right of the checkbox.
217  size_t offset;
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,
222      link_text,
223      &offset);
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()),
231                           link_style);
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);
240
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));
250
251  // Separator.
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);
256
257  // Reporting row.
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);
264
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);
270}
271
272void SessionCrashedBubbleView::ButtonPressed(views::Button* sender,
273                                             const ui::Event& event) {
274  DCHECK(sender);
275  if (sender == restore_button_)
276    RestorePreviousSession(sender);
277  else if (sender == close_)
278    CloseBubble();
279}
280
281void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range& range,
282                                                      int event_flags) {
283  browser_->OpenURL(content::OpenURLParams(
284      GURL("https://support.google.com/chrome/answer/96817"),
285      content::Referrer(),
286      NEW_FOREGROUND_TAB,
287      content::PAGE_TRANSITION_LINK,
288      false));
289}
290
291void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
292      const GURL& url,
293      content::NavigationController::ReloadType reload_type) {
294  started_navigation_ = true;
295}
296
297void SessionCrashedBubbleView::DidFinishLoad(
298      int64 frame_id,
299      const GURL& validated_url,
300      bool is_main_frame,
301      content::RenderViewHost* render_view_host) {
302  if (started_navigation_)
303    CloseBubble();
304}
305
306void SessionCrashedBubbleView::WasShown() {
307  GetWidget()->Show();
308}
309
310void SessionCrashedBubbleView::WasHidden() {
311  GetWidget()->Hide();
312}
313
314void SessionCrashedBubbleView::Observe(
315    int type,
316    const content::NotificationSource& source,
317    const content::NotificationDetails& details) {
318  if (type == chrome::NOTIFICATION_TAB_CLOSING)
319    CloseBubble();
320}
321
322void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents,
323                                             int index) {
324  if (web_contents_ == contents)
325    CloseBubble();
326}
327
328void SessionCrashedBubbleView::RestorePreviousSession(views::Button* sender) {
329  SessionRestore::RestoreSessionAfterCrash(browser_);
330
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    OptionsUtil::ResolveMetricsReportingEnabled(true);
335  CloseBubble();
336}
337
338void SessionCrashedBubbleView::CloseBubble() {
339  GetWidget()->Close();
340}
341
342bool ShowSessionCrashedBubble(Browser* browser) {
343  SessionCrashedBubbleView::Show(browser);
344  return true;
345}
346