1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/views/sad_tab_view.h"
9 #include "base/metrics/histogram.h"
10 #include "chrome/browser/ui/browser_finder.h"
11 #include "chrome/browser/ui/chrome_pages.h"
12 #include "chrome/common/url_constants.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "components/feedback/feedback_util.h"
15 #include "content/public/browser/navigation_controller.h"
16 #include "content/public/browser/web_contents.h"
17 #include "grit/components_strings.h"
18 #include "grit/theme_resources.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/views/background.h"
22 #include "ui/views/controls/button/blue_button.h"
23 #include "ui/views/controls/image_view.h"
24 #include "ui/views/controls/label.h"
25 #include "ui/views/controls/link.h"
26 #include "ui/views/layout/grid_layout.h"
27 #include "ui/views/layout/layout_constants.h"
28 #include "ui/views/widget/widget.h"
30 #if defined(OS_CHROMEOS)
31 #include "chrome/browser/memory/oom_memory_details.h"
34 using content::OpenURLParams
;
35 using content::WebContents
;
39 const int kMaxContentWidth
= 600;
40 const int kMinColumnWidth
= 120;
41 const char kCategoryTagCrash
[] = "Crash";
42 const int kCrashesBeforeFeedbackIsDisplayed
= 1;
44 void RecordKillCreated() {
45 static int killed
= 0;
47 UMA_HISTOGRAM_CUSTOM_COUNTS(
48 "Tabs.SadTab.KillCreated", killed
, 1, 1000, 50);
51 void RecordKillDisplayed() {
52 static int killed
= 0;
54 UMA_HISTOGRAM_CUSTOM_COUNTS(
55 "Tabs.SadTab.KillDisplayed", killed
, 1, 1000, 50);
58 #if defined(OS_CHROMEOS)
59 void RecordKillCreatedOOM() {
60 static int oom_killed
= 0;
62 UMA_HISTOGRAM_CUSTOM_COUNTS(
63 "Tabs.SadTab.KillCreated.OOM", oom_killed
, 1, 1000, 50);
66 void RecordKillDisplayedOOM() {
67 static int oom_killed
= 0;
69 UMA_HISTOGRAM_CUSTOM_COUNTS(
70 "Tabs.SadTab.KillDisplayed.OOM", oom_killed
, 1, 1000, 50);
76 SadTabView::SadTabView(WebContents
* web_contents
, chrome::SadTabKind kind
)
77 : web_contents_(web_contents
),
82 action_button_(nullptr),
84 help_message_(nullptr) {
87 // These stats should use the same counting approach and bucket size used for
88 // tab discard events in memory::OomPriorityManager so they can be directly
90 // TODO(jamescook): Maybe track time between sad tabs?
91 static int total_crashes
= 0;
95 case chrome::SAD_TAB_KIND_CRASHED
: {
96 static int crashed
= 0;
98 UMA_HISTOGRAM_CUSTOM_COUNTS(
99 "Tabs.SadTab.CrashCreated", crashed
, 1, 1000, 50);
102 case chrome::SAD_TAB_KIND_KILLED
: {
104 LOG(WARNING
) << "Tab Killed: "
105 << web_contents
->GetURL().GetOrigin().spec();
108 #if defined(OS_CHROMEOS)
109 case chrome::SAD_TAB_KIND_KILLED_BY_OOM
: {
111 RecordKillCreatedOOM();
112 const std::string spec
= web_contents
->GetURL().GetOrigin().spec();
113 memory::OomMemoryDetails::Log(
114 "Tab OOM-Killed Memory details: " + spec
+ ", ", base::Closure());
120 // Set the background color.
122 views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
123 ui::NativeTheme::kColorId_DialogBackground
)));
125 views::GridLayout
* layout
= new views::GridLayout(this);
126 SetLayoutManager(layout
);
128 const int column_set_id
= 0;
129 views::ColumnSet
* columns
= layout
->AddColumnSet(column_set_id
);
130 columns
->AddPaddingColumn(1, views::kPanelSubVerticalSpacing
);
131 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::LEADING
, 0,
132 views::GridLayout::USE_PREF
, 0, kMinColumnWidth
);
133 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::LEADING
, 0,
134 views::GridLayout::USE_PREF
, 0, kMinColumnWidth
);
135 columns
->AddPaddingColumn(1, views::kPanelSubVerticalSpacing
);
137 views::ImageView
* image
= new views::ImageView();
139 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(IDR_SAD_TAB
));
140 layout
->AddPaddingRow(1, views::kPanelVerticalSpacing
);
141 layout
->StartRow(0, column_set_id
);
142 layout
->AddView(image
, 2, 1);
144 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
146 title_
= CreateLabel(l10n_util::GetStringUTF16(IDS_SAD_TAB_TITLE
));
147 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::LargeFont
));
148 title_
->SetMultiLine(true);
149 title_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
150 layout
->StartRowWithPadding(0, column_set_id
, 0,
151 views::kPanelVerticalSpacing
);
152 layout
->AddView(title_
, 2, 1);
154 const SkColor text_color
= GetNativeTheme()->GetSystemColor(
155 ui::NativeTheme::kColorId_LabelDisabledColor
);
157 int message_id
= IDS_SAD_TAB_MESSAGE
;
158 #if defined(OS_CHROMEOS)
159 if (kind_
== chrome::SAD_TAB_KIND_KILLED_BY_OOM
)
160 message_id
= IDS_KILLED_TAB_BY_OOM_MESSAGE
;
163 message_
= CreateLabel(l10n_util::GetStringUTF16(message_id
));
165 message_
->SetMultiLine(true);
166 message_
->SetEnabledColor(text_color
);
167 message_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
168 message_
->SetLineHeight(views::kPanelSubVerticalSpacing
);
170 layout
->StartRowWithPadding(0, column_set_id
, 0, views::kPanelVertMargin
);
171 layout
->AddView(message_
, 2, 1, views::GridLayout::LEADING
,
172 views::GridLayout::LEADING
);
175 // In the cases of multiple crashes in a session the 'Feedback' button
176 // replaces the 'Reload' button as primary action.
177 int button_type
= total_crashes
> kCrashesBeforeFeedbackIsDisplayed
?
178 SAD_TAB_BUTTON_FEEDBACK
: SAD_TAB_BUTTON_RELOAD
;
179 action_button_
= new views::BlueButton(this,
180 l10n_util::GetStringUTF16(button_type
== SAD_TAB_BUTTON_FEEDBACK
181 ? IDS_CRASHED_TAB_FEEDBACK_LINK
182 : IDS_SAD_TAB_RELOAD_LABEL
));
183 action_button_
->set_tag(button_type
);
185 CreateLink(l10n_util::GetStringUTF16(IDS_LEARN_MORE
), text_color
);
186 layout
->StartRowWithPadding(0, column_set_id
, 0,
187 views::kPanelVerticalSpacing
);
188 layout
->AddView(help_link_
, 1, 1, views::GridLayout::LEADING
,
189 views::GridLayout::CENTER
);
190 layout
->AddView(action_button_
, 1, 1, views::GridLayout::TRAILING
,
191 views::GridLayout::LEADING
);
193 layout
->AddPaddingRow(2, views::kPanelSubVerticalSpacing
);
196 SadTabView::~SadTabView() {}
198 void SadTabView::LinkClicked(views::Link
* source
, int event_flags
) {
199 DCHECK(web_contents_
);
200 OpenURLParams
params(GURL(chrome::kCrashReasonURL
), content::Referrer(),
201 CURRENT_TAB
, ui::PAGE_TRANSITION_LINK
, false);
202 web_contents_
->OpenURL(params
);
205 void SadTabView::ButtonPressed(views::Button
* sender
,
206 const ui::Event
& event
) {
207 DCHECK(web_contents_
);
208 DCHECK_EQ(action_button_
, sender
);
210 if (action_button_
->tag() == SAD_TAB_BUTTON_FEEDBACK
) {
211 chrome::ShowFeedbackPage(
212 chrome::FindBrowserWithWebContents(web_contents_
),
213 l10n_util::GetStringUTF8(kind_
== chrome::SAD_TAB_KIND_CRASHED
?
214 IDS_CRASHED_TAB_FEEDBACK_MESSAGE
: IDS_KILLED_TAB_FEEDBACK_MESSAGE
),
215 std::string(kCategoryTagCrash
));
217 web_contents_
->GetController().Reload(true);
221 void SadTabView::Layout() {
222 // Specify the maximum message width explicitly.
223 const int max_width
=
224 std::min(width() - views::kPanelSubVerticalSpacing
* 2, kMaxContentWidth
);
225 message_
->SizeToFit(max_width
);
226 title_
->SizeToFit(max_width
);
228 if (help_message_
!= nullptr)
229 help_message_
->SizeToFit(max_width
);
234 void SadTabView::OnPaint(gfx::Canvas
* canvas
) {
236 // These stats should use the same counting approach and bucket size used
237 // for tab discard events in memory::OomPriorityManager so they can be
238 // directly compared.
240 case chrome::SAD_TAB_KIND_CRASHED
: {
241 static int crashed
= 0;
242 UMA_HISTOGRAM_CUSTOM_COUNTS(
243 "Tabs.SadTab.CrashDisplayed", ++crashed
, 1, 1000, 50);
246 case chrome::SAD_TAB_KIND_KILLED
:
247 RecordKillDisplayed();
249 #if defined(OS_CHROMEOS)
250 case chrome::SAD_TAB_KIND_KILLED_BY_OOM
:
251 RecordKillDisplayed();
252 RecordKillDisplayedOOM();
258 View::OnPaint(canvas
);
261 void SadTabView::Show() {
262 views::Widget::InitParams
sad_tab_params(
263 views::Widget::InitParams::TYPE_CONTROL
);
265 // It is not possible to create a native_widget_win that has no parent in
266 // and later re-parent it.
267 // TODO(avi): This is a cheat. Can this be made cleaner?
268 sad_tab_params
.parent
= web_contents_
->GetNativeView();
270 set_owned_by_client();
272 views::Widget
* sad_tab
= new views::Widget
;
273 sad_tab
->Init(sad_tab_params
);
274 sad_tab
->SetContentsView(this);
276 views::Widget::ReparentNativeView(sad_tab
->GetNativeView(),
277 web_contents_
->GetNativeView());
278 gfx::Rect bounds
= web_contents_
->GetContainerBounds();
279 sad_tab
->SetBounds(gfx::Rect(bounds
.size()));
282 void SadTabView::Close() {
284 GetWidget()->Close();
287 views::Label
* SadTabView::CreateLabel(const base::string16
& text
) {
288 views::Label
* label
= new views::Label(text
);
289 label
->SetBackgroundColor(background()->get_color());
293 views::Link
* SadTabView::CreateLink(const base::string16
& text
,
294 const SkColor
& color
) {
295 views::Link
* link
= new views::Link(text
);
296 link
->SetBackgroundColor(background()->get_color());
297 link
->SetEnabledColor(color
);
298 link
->set_listener(this);
304 SadTab
* SadTab::Create(content::WebContents
* web_contents
,
306 return new SadTabView(web_contents
, kind
);
309 } // namespace chrome