blob: 6fdd0a2bd455b5018d8e2023c09436bcf88e2a7a [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/session_crashed_bubble_view.h"
#include <vector>
#include "base/prefs/pref_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/ui/options/options_util.h"
#include "chrome/browser/ui/startup/session_crashed_bubble.h"
#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/installer/util/google_update_settings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/web_contents.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/google_chrome_strings.h"
#include "grit/ui_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
using views::GridLayout;
namespace {
// Fixed width of the column holding the description label of the bubble.
const int kWidthOfDescriptionText = 320;
// Distance between checkbox and the text to the right of it.
const int kCheckboxTextDistance = 4;
// Margins width for the top rows to compensate for the bottom panel for which
// we don't want any margin.
const int kMarginWidth = 12;
const int kMarginHeight = kMarginWidth;
// The color of the background of the sub panel to offer UMA optin.
const SkColor kLightGrayBackgroundColor = 0xFFF0F0F0;
const SkColor kWhiteBackgroundColor = 0xFFFFFFFF;
bool ShouldOfferMetricsReporting() {
// Stats collection only applies to Google Chrome builds.
#if defined(GOOGLE_CHROME_BUILD)
// Only show metrics reporting option if user didn't already consent to it.
if (GoogleUpdateSettings::GetCollectStatsConsent())
return false;
return g_browser_process->local_state()->FindPreference(
prefs::kMetricsReportingEnabled)->IsUserModifiable();
#else
return false;
#endif // defined(GOOGLE_CHROME_BUILD)
}
} // namespace
// static
void SessionCrashedBubbleView::Show(Browser* browser) {
if (browser->profile()->IsOffTheRecord())
return;
views::View* anchor_view =
BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu();
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
SessionCrashedBubbleView* crash_bubble =
new SessionCrashedBubbleView(anchor_view, browser, web_contents);
views::BubbleDelegateView::CreateBubble(crash_bubble)->Show();
}
SessionCrashedBubbleView::SessionCrashedBubbleView(
views::View* anchor_view,
Browser* browser,
content::WebContents* web_contents)
: BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
content::WebContentsObserver(web_contents),
browser_(browser),
web_contents_(web_contents),
restore_button_(NULL),
close_(NULL),
uma_option_(NULL),
started_navigation_(false) {
set_close_on_deactivate(false);
registrar_.Add(
this,
chrome::NOTIFICATION_TAB_CLOSING,
content::Source<content::NavigationController>(&(
web_contents->GetController())));
browser->tab_strip_model()->AddObserver(this);
}
SessionCrashedBubbleView::~SessionCrashedBubbleView() {
browser_->tab_strip_model()->RemoveObserver(this);
}
views::View* SessionCrashedBubbleView::GetInitiallyFocusedView() {
return restore_button_;
}
void SessionCrashedBubbleView::Init() {
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
// Close button.
close_ = new views::LabelButton(this, base::string16());
close_->SetImage(views::CustomButton::STATE_NORMAL,
*rb->GetImageNamed(IDR_CLOSE_2).ToImageSkia());
close_->SetImage(views::CustomButton::STATE_HOVERED,
*rb->GetImageNamed(IDR_CLOSE_2_H).ToImageSkia());
close_->SetImage(views::CustomButton::STATE_PRESSED,
*rb->GetImageNamed(IDR_CLOSE_2_P).ToImageSkia());
close_->SetSize(close_->GetPreferredSize());
close_->SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0));
// Bubble title label.
views::Label* title_label = new views::Label(
l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE));
title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::BoldFont));
// Description text label.
views::Label* text_label = new views::Label(
l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE));
text_label->SetMultiLine(true);
text_label->SetLineHeight(20);
text_label->SetEnabledColor(SK_ColorDKGRAY);
text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// Restore button.
restore_button_ = new views::LabelButton(
this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON));
restore_button_->SetStyle(views::Button::STYLE_BUTTON);
restore_button_->SetIsDefault(true);
restore_button_->SetFontList(rb->GetFontList(ui::ResourceBundle::BoldFont));
GridLayout* layout = new GridLayout(this);
SetLayoutManager(layout);
// Title and close button row.
const int kTitleColumnSetId = 0;
views::ColumnSet* cs = layout->AddColumnSet(kTitleColumnSetId);
cs->AddPaddingColumn(0, kMarginWidth);
cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
GridLayout::USE_PREF, 0, 0);
cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0,
GridLayout::USE_PREF, 0, 0);
// Text row.
const int kTextColumnSetId = 1;
cs = layout->AddColumnSet(kTextColumnSetId);
cs->AddPaddingColumn(0, kMarginWidth);
cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
GridLayout::FIXED, kWidthOfDescriptionText, 0);
// Restore button row
const int kButtonColumnSetId = 2;
cs = layout->AddColumnSet(kButtonColumnSetId);
cs->AddPaddingColumn(0, kMarginWidth);
cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
GridLayout::USE_PREF, 0, 0);
cs->AddPaddingColumn(0, kMarginWidth);
layout->AddPaddingRow(0, kMarginHeight);
layout->StartRow(0, kTitleColumnSetId);
layout->AddView(title_label);
layout->AddView(close_);
layout->AddPaddingRow(0, kMarginHeight);
layout->StartRow(0, kTextColumnSetId);
layout->AddView(text_label);
layout->AddPaddingRow(0, kMarginHeight);
layout->StartRow(0, kButtonColumnSetId);
layout->AddView(restore_button_);
layout->AddPaddingRow(0, kMarginHeight);
// Metrics reporting option.
if (ShouldOfferMetricsReporting())
CreateUmaOptinView(layout);
set_color(kWhiteBackgroundColor);
set_margins(gfx::Insets());
Layout();
}
void SessionCrashedBubbleView::CreateUmaOptinView(GridLayout* layout) {
// Checkbox for metric reporting setting.
// Since the text to the right of the checkbox can't be a simple string (needs
// a hyperlink in it), this checkbox contains an empty string as its label,
// and the real text will be added as a separate view.
uma_option_ = new views::Checkbox(base::string16());
uma_option_->SetTextColor(views::Button::STATE_NORMAL, SK_ColorGRAY);
uma_option_->SetChecked(false);
uma_option_->set_background(
views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
uma_option_->set_listener(this);
// The text to the right of the checkbox.
size_t offset;
base::string16 link_text =
l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT);
base::string16 uma_text = l10n_util::GetStringFUTF16(
IDS_SESSION_CRASHED_VIEW_UMA_OPTIN,
link_text,
&offset);
views::StyledLabel* uma_label = new views::StyledLabel(uma_text, this);
uma_label->set_background(
views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
views::StyledLabel::RangeStyleInfo link_style =
views::StyledLabel::RangeStyleInfo::CreateForLink();
link_style.font_style = gfx::Font::NORMAL;
uma_label->AddStyleRange(gfx::Range(offset, offset + link_text.length()),
link_style);
views::StyledLabel::RangeStyleInfo uma_style;
uma_style.color = SK_ColorGRAY;
gfx::Range before_link_range(0, offset);
if (!before_link_range.is_empty())
uma_label->AddStyleRange(before_link_range, uma_style);
gfx::Range after_link_range(offset + link_text.length(), uma_text.length());
if (!after_link_range.is_empty())
uma_label->AddStyleRange(after_link_range, uma_style);
// We use a border instead of padding so that the background color reach
// the edges of the bubble.
uma_option_->SetBorder(
views::Border::CreateSolidSidedBorder(0, kMarginWidth, 0, 0,
kLightGrayBackgroundColor));
uma_label->SetBorder(
views::Border::CreateSolidSidedBorder(
kMarginHeight, kCheckboxTextDistance, kMarginHeight, kMarginWidth,
kLightGrayBackgroundColor));
// Separator.
const int kSeparatorColumnSetId = 3;
views::ColumnSet* cs = layout->AddColumnSet(kSeparatorColumnSetId);
cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
GridLayout::FIXED, kWidthOfDescriptionText + kMarginWidth, 0);
// Reporting row.
const int kReportColumnSetId = 4;
cs = layout->AddColumnSet(kReportColumnSetId);
cs->AddColumn(GridLayout::CENTER, GridLayout::FILL, 0,
GridLayout::USE_PREF, 0, 0);
cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
GridLayout::FIXED, kWidthOfDescriptionText, 0);
layout->StartRow(0, kSeparatorColumnSetId);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
layout->StartRow(0, kReportColumnSetId);
layout->AddView(uma_option_);
layout->AddView(uma_label);
}
void SessionCrashedBubbleView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
DCHECK(sender);
if (sender == restore_button_)
RestorePreviousSession(sender);
else if (sender == close_)
CloseBubble();
}
void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range& range,
int event_flags) {
browser_->OpenURL(content::OpenURLParams(
GURL("https://support.google.com/chrome/answer/96817"),
content::Referrer(),
NEW_FOREGROUND_TAB,
content::PAGE_TRANSITION_LINK,
false));
}
void SessionCrashedBubbleView::DidStartNavigationToPendingEntry(
const GURL& url,
content::NavigationController::ReloadType reload_type) {
started_navigation_ = true;
}
void SessionCrashedBubbleView::DidFinishLoad(
int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
content::RenderViewHost* render_view_host) {
if (started_navigation_)
CloseBubble();
}
void SessionCrashedBubbleView::WasShown() {
GetWidget()->Show();
}
void SessionCrashedBubbleView::WasHidden() {
GetWidget()->Hide();
}
void SessionCrashedBubbleView::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == chrome::NOTIFICATION_TAB_CLOSING)
CloseBubble();
}
void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents,
int index) {
if (web_contents_ == contents)
CloseBubble();
}
void SessionCrashedBubbleView::RestorePreviousSession(views::Button* sender) {
SessionRestore::RestoreSessionAfterCrash(browser_);
// Record user's choice for opting in to UMA.
// There's no opting-out choice in the crash restore bubble.
if (uma_option_ && uma_option_->checked())
OptionsUtil::ResolveMetricsReportingEnabled(true);
CloseBubble();
}
void SessionCrashedBubbleView::CloseBubble() {
GetWidget()->Close();
}
bool ShowSessionCrashedBubble(Browser* browser) {
SessionCrashedBubbleView::Show(browser);
return true;
}