blob: 4e43761db1c9dc7e6b21f337f149e2d23e67a629 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/sad_tab.h"
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/net/referrer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "components/ui_metrics/sadtab_metrics_types.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/memory/oom_memory_details.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#endif
namespace {
void RecordEvent(bool feedback, ui_metrics::SadTabEvent event) {
if (feedback) {
UMA_HISTOGRAM_ENUMERATION(ui_metrics::kSadTabFeedbackHistogramKey, event,
ui_metrics::SadTabEvent::MAX_SAD_TAB_EVENT);
} else {
UMA_HISTOGRAM_ENUMERATION(ui_metrics::kSadTabReloadHistogramKey, event,
ui_metrics::SadTabEvent::MAX_SAD_TAB_EVENT);
}
}
constexpr char kCategoryTagCrash[] = "Crash";
// Return true if this function has been called in the last 10 seconds.
bool IsRepeatedlyCrashing() {
const int kMaxSecondsSinceLastCrash = 10;
static int64_t last_called_ts = 0;
base::TimeTicks last_called(base::TimeTicks::UnixEpoch());
if (last_called_ts)
last_called = base::TimeTicks::FromInternalValue(last_called_ts);
bool crashed_recently = (base::TimeTicks().Now() - last_called).InSeconds() <
kMaxSecondsSinceLastCrash;
last_called_ts = base::TimeTicks().Now().ToInternalValue();
return crashed_recently;
}
bool AreOtherTabsOpen() {
size_t tab_count = 0;
for (Browser* browser : *BrowserList::GetInstance()) {
tab_count += browser->tab_strip_model()->count();
if (tab_count > 1U)
break;
}
return (tab_count > 1U);
}
} // namespace
// static
bool SadTab::ShouldShow(base::TerminationStatus status) {
switch (status) {
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
#if BUILDFLAG(IS_CHROMEOS)
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
#endif
case base::TERMINATION_STATUS_PROCESS_CRASHED:
#if BUILDFLAG(IS_WIN)
case base::TERMINATION_STATUS_INTEGRITY_FAILURE:
#endif
case base::TERMINATION_STATUS_OOM:
return true;
case base::TERMINATION_STATUS_NORMAL_TERMINATION:
case base::TERMINATION_STATUS_STILL_RUNNING:
#if BUILDFLAG(IS_ANDROID)
case base::TERMINATION_STATUS_OOM_PROTECTED:
#endif
case base::TERMINATION_STATUS_LAUNCH_FAILED:
case base::TERMINATION_STATUS_MAX_ENUM:
return false;
}
NOTREACHED();
return false;
}
int SadTab::GetTitle() {
if (!is_repeatedly_crashing_)
return IDS_SAD_TAB_TITLE;
switch (kind_) {
#if BUILDFLAG(IS_CHROMEOS)
case SAD_TAB_KIND_KILLED_BY_OOM:
return IDS_SAD_TAB_RELOAD_TITLE;
#endif
case SAD_TAB_KIND_OOM:
#if BUILDFLAG(IS_WIN) // Only Windows has OOM sad tab strings.
return IDS_SAD_TAB_OOM_TITLE;
#endif
case SAD_TAB_KIND_CRASHED:
case SAD_TAB_KIND_KILLED:
return IDS_SAD_TAB_RELOAD_TITLE;
}
NOTREACHED();
return 0;
}
int SadTab::GetErrorCodeFormatString() {
return IDS_SAD_TAB_ERROR_CODE;
}
int SadTab::GetInfoMessage() {
switch (kind_) {
#if BUILDFLAG(IS_CHROMEOS)
case SAD_TAB_KIND_KILLED_BY_OOM:
return IDS_KILLED_TAB_BY_OOM_MESSAGE;
#endif
case SAD_TAB_KIND_OOM:
if (is_repeatedly_crashing_)
return AreOtherTabsOpen() ? IDS_SAD_TAB_OOM_MESSAGE_TABS
: IDS_SAD_TAB_OOM_MESSAGE_NOTABS;
return IDS_SAD_TAB_MESSAGE;
case SAD_TAB_KIND_CRASHED:
case SAD_TAB_KIND_KILLED:
return is_repeatedly_crashing_ ? IDS_SAD_TAB_RELOAD_TRY
: IDS_SAD_TAB_MESSAGE;
}
NOTREACHED();
return 0;
}
int SadTab::GetButtonTitle() {
return show_feedback_button_ ? IDS_CRASHED_TAB_FEEDBACK_LINK
: IDS_SAD_TAB_RELOAD_LABEL;
}
int SadTab::GetHelpLinkTitle() {
return IDS_LEARN_MORE;
}
const char* SadTab::GetHelpLinkURL() {
return show_feedback_button_ ? chrome::kCrashReasonFeedbackDisplayedURL
: chrome::kCrashReasonURL;
}
std::vector<int> SadTab::GetSubMessages() {
if (!is_repeatedly_crashing_)
return std::vector<int>();
switch (kind_) {
#if BUILDFLAG(IS_CHROMEOS)
case SAD_TAB_KIND_KILLED_BY_OOM:
return std::vector<int>();
#endif
case SAD_TAB_KIND_OOM:
return std::vector<int>();
case SAD_TAB_KIND_CRASHED:
case SAD_TAB_KIND_KILLED:
std::vector<int> message_ids = {IDS_SAD_TAB_RELOAD_RESTART_BROWSER,
IDS_SAD_TAB_RELOAD_RESTART_DEVICE};
// Only show Incognito suggestion if not already in Incognito mode.
if (!web_contents_->GetBrowserContext()->IsOffTheRecord())
message_ids.insert(message_ids.begin(), IDS_SAD_TAB_RELOAD_INCOGNITO);
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// Note: on macOS, Linux and ChromeOS, the first bullet is either one of
// IDS_SAD_TAB_RELOAD_CLOSE_TABS or IDS_SAD_TAB_RELOAD_CLOSE_NOTABS
// followed by one of the above suggestions.
message_ids.insert(message_ids.begin(),
AreOtherTabsOpen() ? IDS_SAD_TAB_RELOAD_CLOSE_TABS
: IDS_SAD_TAB_RELOAD_CLOSE_NOTABS);
#endif
return message_ids;
}
NOTREACHED();
return std::vector<int>();
}
int SadTab::GetCrashedErrorCode() {
return web_contents_->GetCrashedErrorCode();
}
void SadTab::RecordFirstPaint() {
DCHECK(!recorded_paint_);
recorded_paint_ = true;
RecordEvent(show_feedback_button_, ui_metrics::SadTabEvent::DISPLAYED);
}
void SadTab::PerformAction(SadTab::Action action) {
DCHECK(recorded_paint_);
switch (action) {
case Action::BUTTON:
RecordEvent(show_feedback_button_,
ui_metrics::SadTabEvent::BUTTON_CLICKED);
if (show_feedback_button_) {
ShowFeedbackPage(
chrome::FindBrowserWithTab(web_contents_),
chrome::kFeedbackSourceSadTabPage,
std::string() /* description_template */,
l10n_util::GetStringUTF8(kind_ == SAD_TAB_KIND_CRASHED
? IDS_CRASHED_TAB_FEEDBACK_MESSAGE
: IDS_KILLED_TAB_FEEDBACK_MESSAGE),
std::string(kCategoryTagCrash), std::string());
} else {
web_contents_->GetController().Reload(content::ReloadType::NORMAL,
true);
}
break;
case Action::HELP_LINK:
RecordEvent(show_feedback_button_,
ui_metrics::SadTabEvent::HELP_LINK_CLICKED);
content::OpenURLParams params(GURL(GetHelpLinkURL()), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_LINK, false);
web_contents_->OpenURL(params, /*navigation_handle_callback=*/{});
break;
}
}
SadTab::SadTab(content::WebContents* web_contents, SadTabKind kind)
: web_contents_(web_contents),
kind_(kind),
is_repeatedly_crashing_(IsRepeatedlyCrashing()),
show_feedback_button_(false),
recorded_paint_(false) {
switch (kind) {
case SAD_TAB_KIND_CRASHED:
case SAD_TAB_KIND_OOM:
break;
#if BUILDFLAG(IS_CHROMEOS)
case SAD_TAB_KIND_KILLED_BY_OOM: {
const std::string spec =
web_contents->GetURL().DeprecatedGetOriginAsURL().spec();
memory::OomMemoryDetails::Log("Tab OOM-Killed Memory details: " + spec +
", ");
[[fallthrough]];
}
#endif
case SAD_TAB_KIND_KILLED:
LOG(WARNING) << "Tab Killed: "
<< web_contents->GetURL().DeprecatedGetOriginAsURL().spec();
break;
}
#if BUILDFLAG(IS_CHROMEOS)
// Sending feedback is not allowed in the ChromeOS Kiosk mode.
if (chromeos::IsKioskSession()) {
return;
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Only Google Chrome-branded browsers may show the Feedback button.
show_feedback_button_ = is_repeatedly_crashing_;
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}