session_crashed_bubble_view.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/ui_resources.h"
28#include "ui/base/l10n/l10n_util.h"
29#include "ui/base/resource/resource_bundle.h"
30#include "ui/views/controls/button/checkbox.h"
31#include "ui/views/controls/button/label_button.h"
32#include "ui/views/controls/label.h"
33#include "ui/views/controls/separator.h"
34#include "ui/views/layout/grid_layout.h"
35#include "ui/views/layout/layout_constants.h"
36#include "ui/views/widget/widget.h"
37
38using views::GridLayout;
39
40namespace {
41
42// Fixed width of the column holding the description label of the bubble.
43const int kWidthOfDescriptionText = 320;
44
45// Margins width for the top rows to compensate for the bottom panel for which
46// we don't want any margin.
47const int kMarginWidth = 12;
48const int kMarginHeight = kMarginWidth;
49
50// The color of the background of the sub panel to offer UMA optin.
51const SkColor kLightGrayBackgroundColor = 0xFFF0F0F0;
52const SkColor kWhiteBackgroundColor = 0xFFFFFFFF;
53
54bool ShouldOfferMetricsReporting() {
55// Stats collection only applies to Google Chrome builds.
56#if defined(GOOGLE_CHROME_BUILD)
57  // Only show metrics reporting option if user didn't already consent to it.
58  if (GoogleUpdateSettings::GetCollectStatsConsent())
59    return false;
60  return g_browser_process->local_state()->FindPreference(
61      prefs::kMetricsReportingEnabled)->IsUserModifiable();
62#else
63  return false;
64#endif  // defined(GOOGLE_CHROME_BUILD)
65}
66
67}  // namespace
68
69// static
70void SessionCrashedBubbleView::Show(Browser* browser) {
71  if (browser->profile()->IsOffTheRecord())
72    return;
73
74  views::View* anchor_view =
75      BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu();
76  content::WebContents* web_contents =
77      browser->tab_strip_model()->GetActiveWebContents();
78  SessionCrashedBubbleView* crash_bubble =
79      new SessionCrashedBubbleView(anchor_view, browser, web_contents);
80  views::BubbleDelegateView::CreateBubble(crash_bubble)->Show();
81}
82
83SessionCrashedBubbleView::SessionCrashedBubbleView(
84    views::View* anchor_view,
85    Browser* browser,
86    content::WebContents* web_contents)
87    : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
88      content::WebContentsObserver(web_contents),
89      browser_(browser),
90      web_contents_(web_contents),
91      restore_button_(NULL),
92      close_(NULL),
93      uma_option_(NULL),
94      started_navigation_(false) {
95  set_close_on_deactivate(false);
96  registrar_.Add(
97      this,
98      chrome::NOTIFICATION_TAB_CLOSING,
99      content::Source<content::NavigationController>(&(
100          web_contents->GetController())));
101  browser->tab_strip_model()->AddObserver(this);
102}
103
104SessionCrashedBubbleView::~SessionCrashedBubbleView() {
105  browser_->tab_strip_model()->RemoveObserver(this);
106}
107
108views::View* SessionCrashedBubbleView::GetInitiallyFocusedView() {
109  return restore_button_;
110}
111
112void SessionCrashedBubbleView::Init() {
113  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
114
115  // Close button.
116  close_ = new views::LabelButton(this, base::string16());
117  close_->SetImage(views::CustomButton::STATE_NORMAL,
118                   *rb->GetImageNamed(IDR_CLOSE_2).ToImageSkia());
119  close_->SetImage(views::CustomButton::STATE_HOVERED,
120                   *rb->GetImageNamed(IDR_CLOSE_2_H).ToImageSkia());
121  close_->SetImage(views::CustomButton::STATE_PRESSED,
122                   *rb->GetImageNamed(IDR_CLOSE_2_P).ToImageSkia());
123  close_->SetSize(close_->GetPreferredSize());
124  close_->SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0));
125
126  // Bubble title label.
127  views::Label* title_label = new views::Label(
128      l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE));
129  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
130  title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::BoldFont));
131
132  // Description text label.
133  views::Label* text_label = new views::Label(
134      l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE));
135  text_label->SetMultiLine(true);
136  text_label->SetLineHeight(20);
137  text_label->SetEnabledColor(SK_ColorDKGRAY);
138  text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
139
140  // Restore button.
141  restore_button_ = new views::LabelButton(
142      this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON));
143  restore_button_->SetStyle(views::Button::STYLE_BUTTON);
144  restore_button_->SetIsDefault(true);
145  restore_button_->SetFontList(rb->GetFontList(ui::ResourceBundle::BoldFont));
146
147  GridLayout* layout = new GridLayout(this);
148  SetLayoutManager(layout);
149
150  // Title and close button row.
151  const int kTitleColumnSetId = 0;
152  views::ColumnSet* cs = layout->AddColumnSet(kTitleColumnSetId);
153  cs->AddPaddingColumn(0, kMarginWidth);
154  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
155                GridLayout::USE_PREF, 0, 0);
156  cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
157  cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0,
158                GridLayout::USE_PREF, 0, 0);
159
160  // Text row.
161  const int kTextColumnSetId = 1;
162  cs = layout->AddColumnSet(kTextColumnSetId);
163  cs->AddPaddingColumn(0, kMarginWidth);
164  cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
165                GridLayout::FIXED, kWidthOfDescriptionText, 0);
166
167  // Restore button row
168  const int kButtonColumnSetId = 2;
169  cs = layout->AddColumnSet(kButtonColumnSetId);
170  cs->AddPaddingColumn(0, kMarginWidth);
171  cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
172  cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
173                GridLayout::USE_PREF, 0, 0);
174  cs->AddPaddingColumn(0, kMarginWidth);
175
176  layout->AddPaddingRow(0, kMarginHeight);
177  layout->StartRow(0, kTitleColumnSetId);
178  layout->AddView(title_label);
179  layout->AddView(close_);
180  layout->AddPaddingRow(0, kMarginHeight);
181
182  layout->StartRow(0, kTextColumnSetId);
183  layout->AddView(text_label);
184  layout->AddPaddingRow(0, kMarginHeight);
185
186  layout->StartRow(0, kButtonColumnSetId);
187  layout->AddView(restore_button_);
188  layout->AddPaddingRow(0, kMarginHeight);
189
190  // Metrics reporting option.
191  if (ShouldOfferMetricsReporting())
192    CreateUmaOptinView(layout);
193
194  set_color(kWhiteBackgroundColor);
195  set_margins(gfx::Insets());
196  Layout();
197}
198
199void SessionCrashedBubbleView::CreateUmaOptinView(GridLayout* layout) {
200  // Checkbox for metric reporting setting.
201  uma_option_ = new views::Checkbox(
202      l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_UMA_OPTIN));
203  uma_option_->SetTextColor(views::Button::STATE_NORMAL, SK_ColorGRAY);
204  uma_option_->SetChecked(false);
205  uma_option_->SetTextMultiLine(true);
206  uma_option_->set_background(
207      views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
208  uma_option_->set_listener(this);
209  // We use a border instead of padding so that the background color reach
210  // the edges of the bubble.
211  uma_option_->SetBorder(
212      views::Border::CreateSolidSidedBorder(
213          kMarginHeight, kMarginWidth, kMarginHeight, kMarginWidth,
214          kLightGrayBackgroundColor));
215
216  // Separator.
217  const int kSeparatorColumnSetId = 3;
218  views::ColumnSet* cs = layout->AddColumnSet(kSeparatorColumnSetId);
219  cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
220                GridLayout::FIXED, kWidthOfDescriptionText + kMarginWidth, 0);
221
222  // Reporting row.
223  const int kReportColumnSetId = 4;
224  cs = layout->AddColumnSet(kReportColumnSetId);
225  cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
226                GridLayout::FIXED, kWidthOfDescriptionText + kMarginWidth, 0);
227
228  layout->StartRow(0, kSeparatorColumnSetId);
229  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
230  layout->StartRow(0, kReportColumnSetId);
231  layout->AddView(uma_option_);
232}
233
234void SessionCrashedBubbleView::ButtonPressed(views::Button* sender,
235                                             const ui::Event& event) {
236  DCHECK(sender);
237  if (sender == restore_button_)
238    RestorePreviousSession(sender);
239  else if (sender == close_)
240    CloseBubble();
241}
242
243void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
244      const GURL& url,
245      content::NavigationController::ReloadType reload_type) {
246  started_navigation_ = true;
247}
248
249void SessionCrashedBubbleView::DidFinishLoad(
250      int64 frame_id,
251      const GURL& validated_url,
252      bool is_main_frame,
253      content::RenderViewHost* render_view_host) {
254  if (started_navigation_)
255    CloseBubble();
256}
257
258void SessionCrashedBubbleView::WasShown() {
259  GetWidget()->Show();
260}
261
262void SessionCrashedBubbleView::WasHidden() {
263  GetWidget()->Hide();
264}
265
266void SessionCrashedBubbleView::Observe(
267    int type,
268    const content::NotificationSource& source,
269    const content::NotificationDetails& details) {
270  if (type == chrome::NOTIFICATION_TAB_CLOSING)
271    CloseBubble();
272}
273
274void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents,
275                                             int index) {
276  if (web_contents_ == contents)
277    CloseBubble();
278}
279
280void SessionCrashedBubbleView::RestorePreviousSession(views::Button* sender) {
281  SessionRestore::RestoreSessionAfterCrash(browser_);
282
283  // Record user's choice for opting in to UMA.
284  // There's no opting-out choice in the crash restore bubble.
285  if (uma_option_ && uma_option_->checked())
286    OptionsUtil::ResolveMetricsReportingEnabled(true);
287  CloseBubble();
288}
289
290void SessionCrashedBubbleView::CloseBubble() {
291  GetWidget()->Close();
292}
293
294bool ShowSessionCrashedBubble(Browser* browser) {
295  SessionCrashedBubbleView::Show(browser);
296  return true;
297}
298