blob: 0976a8f11c0fd5484a983ef9187cc6d200e774db [file] [log] [blame]
// Copyright (c) 2012 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/sad_tab_view.h"
#include <string>
#include "base/metrics/histogram.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/feedback/feedback_util.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icons_public.h"
#include "ui/native_theme/common_theme.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/blue_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/memory/oom_memory_details.h"
#endif
using content::OpenURLParams;
using content::WebContents;
namespace {
const int kMaxContentWidth = 600;
const int kMinColumnWidth = 120;
const char kCategoryTagCrash[] = "Crash";
const int kCrashesBeforeFeedbackIsDisplayed = 1;
void RecordKillCreated() {
static int killed = 0;
killed++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.KillCreated", killed);
}
void RecordKillDisplayed() {
static int killed = 0;
killed++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.KillDisplayed", killed);
}
#if defined(OS_CHROMEOS)
void RecordKillCreatedOOM() {
static int oom_killed = 0;
oom_killed++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.KillCreated.OOM", oom_killed);
}
void RecordKillDisplayedOOM() {
static int oom_killed = 0;
oom_killed++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.KillDisplayed.OOM", oom_killed);
}
#endif
} // namespace
int SadTabView::total_crashes_ = 0;
SadTabView::SadTabView(WebContents* web_contents, chrome::SadTabKind kind)
: web_contents_(web_contents),
kind_(kind),
painted_(false),
message_(nullptr),
help_link_(nullptr),
action_button_(nullptr),
title_(nullptr),
help_message_(nullptr) {
DCHECK(web_contents);
// These stats should use the same counting approach and bucket size used for
// tab discard events in memory::OomPriorityManager so they can be directly
// compared.
// TODO(jamescook): Maybe track time between sad tabs?
total_crashes_++;
switch (kind_) {
case chrome::SAD_TAB_KIND_CRASHED: {
static int crashed = 0;
crashed++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.CrashCreated", crashed);
break;
}
case chrome::SAD_TAB_KIND_KILLED: {
RecordKillCreated();
LOG(WARNING) << "Tab Killed: "
<< web_contents->GetURL().GetOrigin().spec();
break;
}
case chrome::SAD_TAB_KIND_OOM: {
static int crashed_due_to_oom = 0;
crashed_due_to_oom++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.OomCreated", crashed_due_to_oom);
break;
}
#if defined(OS_CHROMEOS)
case chrome::SAD_TAB_KIND_KILLED_BY_OOM: {
RecordKillCreated();
RecordKillCreatedOOM();
const std::string spec = web_contents->GetURL().GetOrigin().spec();
memory::OomMemoryDetails::Log(
"Tab OOM-Killed Memory details: " + spec + ", ", base::Closure());
break;
}
#endif
}
// Set the background color.
set_background(
views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground)));
views::GridLayout* layout = new views::GridLayout(this);
SetLayoutManager(layout);
const int column_set_id = 0;
views::ColumnSet* columns = layout->AddColumnSet(column_set_id);
columns->AddPaddingColumn(1, views::kPanelSubVerticalSpacing);
columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
views::GridLayout::USE_PREF, 0, kMinColumnWidth);
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::LEADING, 0,
views::GridLayout::USE_PREF, 0, kMinColumnWidth);
columns->AddPaddingColumn(1, views::kPanelSubVerticalSpacing);
views::ImageView* image = new views::ImageView();
image->SetImage(gfx::CreateVectorIcon(gfx::VectorIconId::CRASHED_TAB, 48,
gfx::kChromeIconGrey));
layout->AddPaddingRow(1, views::kPanelVerticalSpacing);
layout->StartRow(0, column_set_id);
layout->AddView(image, 2, 1);
title_ = CreateLabel(l10n_util::GetStringUTF16(IDS_SAD_TAB_TITLE));
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
title_->SetFontList(rb.GetFontList(ui::ResourceBundle::LargeFont));
title_->SetMultiLine(true);
title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
layout->StartRowWithPadding(0, column_set_id, 0,
views::kPanelVerticalSpacing);
layout->AddView(title_, 2, 1);
const SkColor text_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelDisabledColor);
int message_id = IDS_SAD_TAB_MESSAGE;
#if defined(OS_CHROMEOS)
if (kind_ == chrome::SAD_TAB_KIND_KILLED_BY_OOM)
message_id = IDS_KILLED_TAB_BY_OOM_MESSAGE;
#endif
if (kind_ == chrome::SAD_TAB_KIND_OOM)
message_id = IDS_SAD_TAB_OOM_MESSAGE;
message_ = CreateLabel(l10n_util::GetStringUTF16(message_id));
message_->SetMultiLine(true);
message_->SetEnabledColor(text_color);
message_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
message_->SetLineHeight(views::kPanelSubVerticalSpacing);
layout->StartRowWithPadding(0, column_set_id, 0, views::kPanelVertMargin);
layout->AddView(message_, 2, 1, views::GridLayout::LEADING,
views::GridLayout::LEADING);
if (web_contents_) {
// In the cases of multiple crashes in a session the 'Feedback' button
// replaces the 'Reload' button as primary action.
int button_type = total_crashes_ > kCrashesBeforeFeedbackIsDisplayed ?
SAD_TAB_BUTTON_FEEDBACK : SAD_TAB_BUTTON_RELOAD;
action_button_ = new views::BlueButton(this,
l10n_util::GetStringUTF16(button_type == SAD_TAB_BUTTON_FEEDBACK
? IDS_CRASHED_TAB_FEEDBACK_LINK
: IDS_SAD_TAB_RELOAD_LABEL));
action_button_->set_tag(button_type);
help_link_ =
CreateLink(l10n_util::GetStringUTF16(IDS_LEARN_MORE), text_color);
layout->StartRowWithPadding(0, column_set_id, 0,
views::kPanelVerticalSpacing);
layout->AddView(help_link_, 1, 1, views::GridLayout::LEADING,
views::GridLayout::CENTER);
layout->AddView(action_button_, 1, 1, views::GridLayout::TRAILING,
views::GridLayout::LEADING);
}
layout->AddPaddingRow(2, views::kPanelSubVerticalSpacing);
}
SadTabView::~SadTabView() {}
void SadTabView::LinkClicked(views::Link* source, int event_flags) {
DCHECK(web_contents_);
OpenURLParams params(GURL(total_crashes_ > kCrashesBeforeFeedbackIsDisplayed
? chrome::kCrashReasonFeedbackDisplayedURL
: chrome::kCrashReasonURL),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_LINK, false);
web_contents_->OpenURL(params);
}
void SadTabView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
DCHECK(web_contents_);
DCHECK_EQ(action_button_, sender);
if (action_button_->tag() == SAD_TAB_BUTTON_FEEDBACK) {
chrome::ShowFeedbackPage(
chrome::FindBrowserWithWebContents(web_contents_),
l10n_util::GetStringUTF8(kind_ == chrome::SAD_TAB_KIND_CRASHED ?
IDS_CRASHED_TAB_FEEDBACK_MESSAGE : IDS_KILLED_TAB_FEEDBACK_MESSAGE),
std::string(kCategoryTagCrash));
} else {
web_contents_->GetController().Reload(true);
}
}
void SadTabView::Layout() {
// Specify the maximum message width explicitly.
const int max_width =
std::min(width() - views::kPanelSubVerticalSpacing * 2, kMaxContentWidth);
message_->SizeToFit(max_width);
title_->SizeToFit(max_width);
if (help_message_ != nullptr)
help_message_->SizeToFit(max_width);
View::Layout();
}
void SadTabView::OnPaint(gfx::Canvas* canvas) {
if (!painted_) {
// These stats should use the same counting approach and bucket size used
// for tab discard events in memory::OomPriorityManager so they can be
// directly compared.
switch (kind_) {
case chrome::SAD_TAB_KIND_CRASHED: {
static int crashed = 0;
crashed++;
UMA_HISTOGRAM_COUNTS_1000("Tabs.SadTab.CrashDisplayed", crashed);
break;
}
case chrome::SAD_TAB_KIND_OOM: {
static int crashed_due_to_oom = 0;
crashed_due_to_oom++;
UMA_HISTOGRAM_COUNTS_1000(
"Tabs.SadTab.OomDisplayed", crashed_due_to_oom);
break;
}
case chrome::SAD_TAB_KIND_KILLED:
RecordKillDisplayed();
break;
#if defined(OS_CHROMEOS)
case chrome::SAD_TAB_KIND_KILLED_BY_OOM:
RecordKillDisplayed();
RecordKillDisplayedOOM();
break;
#endif
}
painted_ = true;
}
View::OnPaint(canvas);
}
void SadTabView::Show() {
views::Widget::InitParams sad_tab_params(
views::Widget::InitParams::TYPE_CONTROL);
// It is not possible to create a native_widget_win that has no parent in
// and later re-parent it.
// TODO(avi): This is a cheat. Can this be made cleaner?
sad_tab_params.parent = web_contents_->GetNativeView();
set_owned_by_client();
views::Widget* sad_tab = new views::Widget;
sad_tab->Init(sad_tab_params);
sad_tab->SetContentsView(this);
views::Widget::ReparentNativeView(sad_tab->GetNativeView(),
web_contents_->GetNativeView());
gfx::Rect bounds = web_contents_->GetContainerBounds();
sad_tab->SetBounds(gfx::Rect(bounds.size()));
}
void SadTabView::Close() {
if (GetWidget())
GetWidget()->Close();
}
views::Label* SadTabView::CreateLabel(const base::string16& text) {
views::Label* label = new views::Label(text);
label->SetBackgroundColor(background()->get_color());
return label;
}
views::Link* SadTabView::CreateLink(const base::string16& text,
const SkColor& color) {
views::Link* link = new views::Link(text);
link->SetBackgroundColor(background()->get_color());
link->SetEnabledColor(color);
link->set_listener(this);
return link;
}
namespace chrome {
SadTab* SadTab::Create(content::WebContents* web_contents,
SadTabKind kind) {
return new SadTabView(web_contents, kind);
}
} // namespace chrome