Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / session_crashed_bubble_view.cc
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/bind.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/session_crashed_bubble.h"
22 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/browser/ui/views/frame/browser_view.h"
25 #include "chrome/browser/ui/views/toolbar/toolbar_view.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"
47
48 using views::GridLayout;
49
50 namespace {
51
52 // Fixed width of the column holding the description label of the bubble.
53 const int kWidthOfDescriptionText = 320;
54
55 // Distance between checkbox and the text to the right of it.
56 const int kCheckboxTextDistance = 4;
57
58 // The color of the text and background of the sub panel to offer UMA optin.
59 // These values match the BookmarkSyncPromoView colors.
60 const SkColor kBackgroundColor = SkColorSetRGB(245, 245, 245);
61 const SkColor kTextColor = SkColorSetRGB(102, 102, 102);
62
63 // The Finch study name and group name that enables session crashed bubble UI.
64 const char kEnableBubbleUIFinchName[] = "EnableSessionCrashedBubbleUI";
65 const char kEnableBubbleUIGroupEnabled[] = "Enabled";
66
67 enum SessionCrashedBubbleHistogramValue {
68   SESSION_CRASHED_BUBBLE_SHOWN,
69   SESSION_CRASHED_BUBBLE_ERROR,
70   SESSION_CRASHED_BUBBLE_RESTORED,
71   SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN,
72   SESSION_CRASHED_BUBBLE_UMA_OPTIN,
73   SESSION_CRASHED_BUBBLE_HELP,
74   SESSION_CRASHED_BUBBLE_IGNORED,
75   SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN,
76   SESSION_CRASHED_BUBBLE_MAX,
77 };
78
79 void RecordBubbleHistogramValue(SessionCrashedBubbleHistogramValue value) {
80   UMA_HISTOGRAM_ENUMERATION(
81       "SessionCrashed.Bubble", value, SESSION_CRASHED_BUBBLE_MAX);
82 }
83
84 // Whether or not the bubble UI should be used.
85 bool IsBubbleUIEnabled() {
86   const base::CommandLine& command_line = *CommandLine::ForCurrentProcess();
87   if (command_line.HasSwitch(switches::kDisableSessionCrashedBubble))
88     return false;
89   if (command_line.HasSwitch(switches::kEnableSessionCrashedBubble))
90     return true;
91   const std::string group_name = base::FieldTrialList::FindFullName(
92       kEnableBubbleUIFinchName);
93   return group_name == kEnableBubbleUIGroupEnabled;
94 }
95
96 }  // namespace
97
98 // A helper class that listens to browser removal event.
99 class SessionCrashedBubbleView::BrowserRemovalObserver
100     : public chrome::BrowserListObserver {
101  public:
102   explicit BrowserRemovalObserver(Browser* browser) : browser_(browser) {
103     DCHECK(browser_);
104     BrowserList::AddObserver(this);
105   }
106
107   virtual ~BrowserRemovalObserver() {
108     BrowserList::RemoveObserver(this);
109   }
110
111   // Overridden from chrome::BrowserListObserver.
112   virtual void OnBrowserRemoved(Browser* browser) OVERRIDE {
113     if (browser == browser_)
114       browser_ = NULL;
115   }
116
117   Browser* browser() const { return browser_; }
118
119  private:
120   Browser* browser_;
121
122   DISALLOW_COPY_AND_ASSIGN(BrowserRemovalObserver);
123 };
124
125 // static
126 void SessionCrashedBubbleView::Show(Browser* browser) {
127   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
128   if (browser->profile()->IsOffTheRecord())
129     return;
130
131   // Observes browser removal event and will be deallocated in ShowForReal.
132   scoped_ptr<BrowserRemovalObserver> browser_observer(
133       new BrowserRemovalObserver(browser));
134
135 // Stats collection only applies to Google Chrome builds.
136 #if defined(GOOGLE_CHROME_BUILD)
137   // Schedule a task to run GoogleUpdateSettings::GetCollectStatsConsent() on
138   // FILE thread, since it does IO. Then, call
139   // SessionCrashedBubbleView::ShowForReal with the result.
140   content::BrowserThread::PostTaskAndReplyWithResult(
141       content::BrowserThread::FILE,
142       FROM_HERE,
143       base::Bind(&GoogleUpdateSettings::GetCollectStatsConsent),
144       base::Bind(&SessionCrashedBubbleView::ShowForReal,
145                  base::Passed(&browser_observer)));
146 #else
147   SessionCrashedBubbleView::ShowForReal(browser_observer.Pass(), false);
148 #endif  // defined(GOOGLE_CHROME_BUILD)
149 }
150
151 // static
152 void SessionCrashedBubbleView::ShowForReal(
153     scoped_ptr<BrowserRemovalObserver> browser_observer,
154     bool uma_opted_in_already) {
155   // Determine whether or not the UMA opt-in option should be offered. It is
156   // offered only when it is a Google chrome build, user hasn't opted in yet,
157   // and the preference is modifiable by the user.
158   bool offer_uma_optin = false;
159
160 #if defined(GOOGLE_CHROME_BUILD)
161   if (!uma_opted_in_already) {
162     offer_uma_optin = g_browser_process->local_state()->FindPreference(
163         prefs::kMetricsReportingEnabled)->IsUserModifiable();
164   }
165 #endif  // defined(GOOGLE_CHROME_BUILD)
166
167   Browser* browser = browser_observer->browser();
168
169   if (!browser) {
170     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
171     return;
172   }
173
174   views::View* anchor_view =
175       BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu();
176   content::WebContents* web_contents =
177       browser->tab_strip_model()->GetActiveWebContents();
178
179   if (!web_contents) {
180     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
181     return;
182   }
183
184   SessionCrashedBubbleView* crash_bubble =
185       new SessionCrashedBubbleView(anchor_view, browser, web_contents,
186                                    offer_uma_optin);
187   views::BubbleDelegateView::CreateBubble(crash_bubble)->Show();
188
189   RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_SHOWN);
190   if (uma_opted_in_already)
191     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ALREADY_UMA_OPTIN);
192 }
193
194 SessionCrashedBubbleView::SessionCrashedBubbleView(
195     views::View* anchor_view,
196     Browser* browser,
197     content::WebContents* web_contents,
198     bool offer_uma_optin)
199     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
200       content::WebContentsObserver(web_contents),
201       browser_(browser),
202       web_contents_(web_contents),
203       restore_button_(NULL),
204       uma_option_(NULL),
205       offer_uma_optin_(offer_uma_optin),
206       started_navigation_(false),
207       restored_(false) {
208   set_close_on_deactivate(false);
209   registrar_.Add(
210       this,
211       chrome::NOTIFICATION_TAB_CLOSING,
212       content::Source<content::NavigationController>(&(
213           web_contents->GetController())));
214   browser->tab_strip_model()->AddObserver(this);
215 }
216
217 SessionCrashedBubbleView::~SessionCrashedBubbleView() {
218   browser_->tab_strip_model()->RemoveObserver(this);
219 }
220
221 views::View* SessionCrashedBubbleView::GetInitiallyFocusedView() {
222   return restore_button_;
223 }
224
225 base::string16 SessionCrashedBubbleView::GetWindowTitle() const {
226   return l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE);
227 }
228
229 bool SessionCrashedBubbleView::ShouldShowWindowTitle() const {
230   return true;
231 }
232
233 bool SessionCrashedBubbleView::ShouldShowCloseButton() const {
234   return true;
235 }
236
237 void SessionCrashedBubbleView::OnWidgetDestroying(views::Widget* widget) {
238   if (!restored_)
239     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_IGNORED);
240   BubbleDelegateView::OnWidgetDestroying(widget);
241 }
242
243 void SessionCrashedBubbleView::Init() {
244   // Description text label.
245   views::Label* text_label = new views::Label(
246       l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE));
247   text_label->SetMultiLine(true);
248   text_label->SetLineHeight(20);
249   text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
250   text_label->SizeToFit(kWidthOfDescriptionText);
251
252   // Restore button.
253   restore_button_ = new views::LabelButton(
254       this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON));
255   restore_button_->SetStyle(views::Button::STYLE_BUTTON);
256   restore_button_->SetIsDefault(true);
257
258   GridLayout* layout = new GridLayout(this);
259   SetLayoutManager(layout);
260
261   // Text row.
262   const int kTextColumnSetId = 0;
263   views::ColumnSet* cs = layout->AddColumnSet(kTextColumnSetId);
264   cs->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
265   cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
266                 GridLayout::FIXED, kWidthOfDescriptionText, 0);
267   cs->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
268
269   // Restore button row.
270   const int kButtonColumnSetId = 1;
271   cs = layout->AddColumnSet(kButtonColumnSetId);
272   cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 1,
273                 GridLayout::USE_PREF, 0, 0);
274   cs->AddPaddingColumn(0, GetBubbleFrameView()->GetTitleInsets().left());
275
276   layout->StartRow(0, kTextColumnSetId);
277   layout->AddView(text_label);
278   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
279
280   layout->StartRow(0, kButtonColumnSetId);
281   layout->AddView(restore_button_);
282   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
283
284   int bottom_margin = 1;
285
286   // Metrics reporting option.
287   if (offer_uma_optin_) {
288     const int kUMAOptionColumnSetId = 2;
289     cs = layout->AddColumnSet(kUMAOptionColumnSetId);
290     cs->AddColumn(
291         GridLayout::FILL, GridLayout::FILL, 1, GridLayout::USE_PREF, 0, 0);
292     layout->StartRow(1, kUMAOptionColumnSetId);
293     layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
294     layout->StartRow(1, kUMAOptionColumnSetId);
295     layout->AddView(CreateUMAOptinView());
296
297     // Since the UMA optin row has a different background than the default
298     // background color of bubbles, the bottom margin has to be 0 to make sure
299     // the background extends to the bottom edge of the bubble.
300     bottom_margin = 0;
301
302     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_OPTIN_BAR_SHOWN);
303   }
304
305   set_margins(gfx::Insets(1, 0, bottom_margin, 0));
306   Layout();
307 }
308
309 views::View* SessionCrashedBubbleView::CreateUMAOptinView() {
310   // Checkbox for metric reporting setting.
311   // Since the text to the right of the checkbox can't be a simple string (needs
312   // a hyperlink in it), this checkbox contains an empty string as its label,
313   // and the real text will be added as a separate view.
314   uma_option_ = new views::Checkbox(base::string16());
315   uma_option_->SetChecked(false);
316
317   // The text to the right of the checkbox.
318   size_t offset;
319   base::string16 link_text =
320       l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT);
321   base::string16 uma_text = l10n_util::GetStringFUTF16(
322       IDS_SESSION_CRASHED_VIEW_UMA_OPTIN,
323       link_text,
324       &offset);
325   views::StyledLabel* uma_label = new views::StyledLabel(uma_text, this);
326   views::StyledLabel::RangeStyleInfo link_style =
327       views::StyledLabel::RangeStyleInfo::CreateForLink();
328   link_style.font_style = gfx::Font::NORMAL;
329   uma_label->AddStyleRange(gfx::Range(offset, offset + link_text.length()),
330                            link_style);
331   views::StyledLabel::RangeStyleInfo uma_style;
332   uma_style.color = kTextColor;
333   gfx::Range before_link_range(0, offset);
334   if (!before_link_range.is_empty())
335     uma_label->AddStyleRange(before_link_range, uma_style);
336   gfx::Range after_link_range(offset + link_text.length(), uma_text.length());
337   if (!after_link_range.is_empty())
338     uma_label->AddStyleRange(after_link_range, uma_style);
339
340   // Create a view to hold the checkbox and the text.
341   views::View* uma_view = new views::View();
342   GridLayout* uma_layout = new GridLayout(uma_view);
343   uma_view->SetLayoutManager(uma_layout);
344
345   uma_view->set_background(
346       views::Background::CreateSolidBackground(kBackgroundColor));
347   int inset_left = GetBubbleFrameView()->GetTitleInsets().left();
348   uma_layout->SetInsets(views::kRelatedControlVerticalSpacing, inset_left,
349                         views::kRelatedControlVerticalSpacing, inset_left);
350
351   const int kReportColumnSetId = 0;
352   views::ColumnSet* cs = uma_layout->AddColumnSet(kReportColumnSetId);
353   cs->AddColumn(GridLayout::CENTER, GridLayout::LEADING, 0,
354                 GridLayout::USE_PREF, 0, 0);
355   cs->AddPaddingColumn(0, kCheckboxTextDistance);
356   cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
357                 GridLayout::FIXED, kWidthOfDescriptionText, 0);
358
359   uma_layout->StartRow(0, kReportColumnSetId);
360   uma_layout->AddView(uma_option_);
361   uma_layout->AddView(uma_label);
362
363   return uma_view;
364 }
365
366 void SessionCrashedBubbleView::ButtonPressed(views::Button* sender,
367                                              const ui::Event& event) {
368   DCHECK_EQ(sender, restore_button_);
369   RestorePreviousSession(sender);
370 }
371
372 void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range& range,
373                                                       int event_flags) {
374   browser_->OpenURL(content::OpenURLParams(
375       GURL("https://support.google.com/chrome/answer/96817"),
376       content::Referrer(),
377       NEW_FOREGROUND_TAB,
378       ui::PAGE_TRANSITION_LINK,
379       false));
380   RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_HELP);
381 }
382
383 void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
384       const GURL& url,
385       content::NavigationController::ReloadType reload_type) {
386   started_navigation_ = true;
387 }
388
389 void SessionCrashedBubbleView::DidFinishLoad(
390     content::RenderFrameHost* render_frame_host,
391     const GURL& validated_url) {
392   if (started_navigation_)
393     CloseBubble();
394 }
395
396 void SessionCrashedBubbleView::WasShown() {
397   GetWidget()->Show();
398 }
399
400 void SessionCrashedBubbleView::WasHidden() {
401   GetWidget()->Hide();
402 }
403
404 void SessionCrashedBubbleView::Observe(
405     int type,
406     const content::NotificationSource& source,
407     const content::NotificationDetails& details) {
408   if (type == chrome::NOTIFICATION_TAB_CLOSING)
409     CloseBubble();
410 }
411
412 void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents,
413                                              int index) {
414   if (web_contents_ == contents)
415     CloseBubble();
416 }
417
418 void SessionCrashedBubbleView::RestorePreviousSession(views::Button* sender) {
419   SessionRestore::RestoreSessionAfterCrash(browser_);
420   RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_RESTORED);
421   restored_ = true;
422
423   // Record user's choice for opting in to UMA.
424   // There's no opting-out choice in the crash restore bubble.
425   if (uma_option_ && uma_option_->checked()) {
426     InitiateMetricsReportingChange(true, OnMetricsReportingCallbackType());
427     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_UMA_OPTIN);
428   }
429   CloseBubble();
430 }
431
432 void SessionCrashedBubbleView::CloseBubble() {
433   GetWidget()->Close();
434 }
435
436 bool ShowSessionCrashedBubble(Browser* browser) {
437   if (IsBubbleUIEnabled()) {
438     SessionCrashedBubbleView::Show(browser);
439     return true;
440   }
441   return false;
442 }