blob: 7d2c75294cb7faa342e4025f2332aa3f637aa822 [file] [log] [blame]
// Copyright 2012 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/views/sad_tab_view.h"
#include <algorithm>
#include <string>
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/result_codes.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/bulleted_label_list/bulleted_label_list_view.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/style/typography.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/components/kiosk/kiosk_utils.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
std::unique_ptr<views::Label> CreateFormattedLabel(
const std::u16string& message) {
auto label = std::make_unique<views::Label>(
message, views::style::CONTEXT_LABEL, views::style::STYLE_SECONDARY);
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetLineHeight(ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_UNRELATED_CONTROL_VERTICAL));
return label;
}
// Show the error code in a selectable label to allow users to copy it.
std::unique_ptr<views::Label> CreateErrorCodeLabel(int format_string,
int error_code) {
auto label = std::make_unique<views::Label>(
l10n_util::GetStringFUTF16(
format_string,
base::UTF8ToUTF16(content::CrashExitCodeToString(error_code))),
views::style::CONTEXT_LABEL, views::style::STYLE_SECONDARY);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetSelectable(true);
label->SetFontList(
gfx::FontList().Derive(-3, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL));
return label;
}
} // namespace
SadTabView::SadTabView(content::WebContents* web_contents, SadTabKind kind)
: SadTab(web_contents, kind) {
// This view gets inserted as a child of a WebView, but we don't want the
// WebView to delete us if the WebView gets deleted before the SadTabHelper
// does.
set_owned_by_client(OwnedByClientPassKey());
SetBackground(views::CreateSolidBackground(ui::kColorDialogBackground));
ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
provider->GetInsetsMetric(views::INSETS_DIALOG)));
auto* top_spacer = AddChildView(std::make_unique<views::View>());
auto* container = AddChildView(std::make_unique<views::FlexLayoutView>());
container->SetOrientation(views::LayoutOrientation::kVertical);
constexpr int kMinContentWidth = 240;
container->SetMinimumCrossAxisSize(kMinContentWidth);
auto* bottom_spacer = AddChildView(std::make_unique<views::View>());
// Center content horizontally; divide vertical padding into 1/3 above, 2/3
// below.
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
layout->SetFlexForView(top_spacer, 1);
layout->SetFlexForView(bottom_spacer, 2);
// Crashed tab image.
auto* image = container->AddChildView(std::make_unique<views::ImageView>());
image->SetImage(
ui::ImageModel::FromVectorIcon(kCrashedTabIcon, ui::kColorIcon, 48));
const int unrelated_vertical_spacing =
provider->GetDistanceMetric(views::DISTANCE_UNRELATED_CONTROL_VERTICAL);
image->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, 0, unrelated_vertical_spacing, 0));
image->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kStart);
// Title.
title_ = container->AddChildView(
std::make_unique<views::Label>(l10n_util::GetStringUTF16(GetTitle())));
title_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
ui::ResourceBundle::LargeFont));
title_->SetMultiLine(true);
title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
constexpr int kTitleBottomSpacing = 13;
title_->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, 0, kTitleBottomSpacing, 0));
// Message and optional bulleted list.
message_ = container->AddChildView(
CreateFormattedLabel(l10n_util::GetStringUTF16(GetInfoMessage())));
std::vector<int> bullet_string_ids = GetSubMessages();
if (!bullet_string_ids.empty()) {
std::vector<std::u16string> texts;
std::ranges::transform(bullet_string_ids, std::back_inserter(texts),
l10n_util::GetStringUTF16);
auto* list_view =
container->AddChildView(std::make_unique<views::BulletedLabelListView>(
std::move(texts), views::style::TextStyle::STYLE_PRIMARY));
list_view->SetProperty(views::kTableColAndRowSpanKey, gfx::Size(2, 1));
}
// Error code.
container
->AddChildView(CreateErrorCodeLabel(GetErrorCodeFormatString(),
GetCrashedErrorCode()))
->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(kTitleBottomSpacing, 0,
unrelated_vertical_spacing, 0));
// Bottom row: help link, action button.
auto* actions_container =
container->AddChildView(std::make_unique<views::FlexLayoutView>());
actions_container->SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
// TODO(crbug.com/363826230): See View::SetLayoutManagerUseConstrainedSpace.
//
// `actions_container` is a horizontal FlexLayout, and its child element
// `action_button` has an unbounded horizontal size. This causes it to consume
// the size of the entire constraint space when we calculate the preferred
// size under the current constraint space. This causes the actual width
// occupied by action_button to be too wide.
//
// There is currently no good way to handle kEnd alignment for a single
// element.
actions_container->SetLayoutManagerUseConstrainedSpace(false);
EnableHelpLink(actions_container);
action_button_ =
actions_container->AddChildView(std::make_unique<views::MdTextButton>(
base::BindRepeating(&SadTabView::PerformAction,
base::Unretained(this), Action::kButton),
l10n_util::GetStringUTF16(GetButtonTitle())));
action_button_->SetStyle(ui::ButtonStyle::kProminent);
action_button_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded)
.WithAlignment(views::LayoutAlignment::kEnd));
// Needed to ensure this View is drawn even if a sibling (such as dev tools)
// has a z-order.
SetPaintToLayer();
AttachToWebView();
if (owner_) {
// If the `owner_` ContentsWebView has a rounded background, the sad tab
// should also have matching rounded corners as well.
SetBackgroundRadii(
static_cast<ContentsWebView*>(owner_)->GetBackgroundRadii());
}
// Make the accessibility role of this view an alert dialog, and
// put focus on the action button. This causes screen readers to
// immediately announce the text of this view.
GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
if (action_button_->GetWidget() && action_button_->GetWidget()->IsActive()) {
action_button_->RequestFocus();
}
}
SadTabView::~SadTabView() {
if (owner_) {
owner_->SetCrashedOverlayView(nullptr);
}
}
void SadTabView::ReinstallInWebView() {
if (owner_) {
owner_->SetCrashedOverlayView(nullptr);
owner_ = nullptr;
}
AttachToWebView();
}
gfx::RoundedCornersF SadTabView::GetBackgroundRadii() const {
CHECK(layer());
return layer()->rounded_corner_radii();
}
void SadTabView::SetBackgroundRadii(const gfx::RoundedCornersF& radii) {
// Since SadTabView paints onto its own layer and it is leaf layer, we can
// round the background by applying rounded corners to the layer without
// clipping any other browser content.
CHECK(layer());
layer()->SetRoundedCornerRadius(radii);
layer()->SetIsFastRoundedCorner(/*enable=*/true);
}
void SadTabView::OnPaint(gfx::Canvas* canvas) {
if (!painted_) {
RecordFirstPaint();
painted_ = true;
}
View::OnPaint(canvas);
}
void SadTabView::RemovedFromWidget() {
owner_ = nullptr;
}
void SadTabView::AttachToWebView() {
Browser* browser = chrome::FindBrowserWithTab(web_contents());
// This can be null during prefetch.
if (!browser) {
return;
}
// In unit tests, browser->window() might not be a real BrowserView.
if (!browser->window()->GetNativeWindow()) {
return;
}
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
DCHECK(browser_view);
std::vector<ContentsWebView*> visible_contents_views =
browser_view->GetAllVisibleContentsWebViews();
for (ContentsWebView* contents_view : visible_contents_views) {
if (contents_view->web_contents() == web_contents()) {
owner_ = contents_view;
owner_->SetCrashedOverlayView(this);
break;
}
}
}
void SadTabView::EnableHelpLink(views::FlexLayoutView* actions_container) {
#if BUILDFLAG(IS_CHROMEOS)
// Do not show the help link in the kiosk session to prevent escape from a
// kiosk app.
if (chromeos::IsKioskSession()) {
return;
}
#endif
auto* help_link =
actions_container->AddChildView(std::make_unique<views::Link>(
l10n_util::GetStringUTF16(GetHelpLinkTitle())));
help_link->SetCallback(base::BindRepeating(
&SadTab::PerformAction, base::Unretained(this), Action::kHelpLink));
help_link->SetProperty(views::kTableVertAlignKey,
views::LayoutAlignment::kCenter);
}
void SadTabView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
// Specify the maximum message and title width explicitly.
constexpr int kMaxContentWidth = 600;
const int max_width =
std::min(width() - ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_UNRELATED_CONTROL_HORIZONTAL) *
2,
kMaxContentWidth);
message_->SizeToFit(max_width);
title_->SizeToFit(max_width);
}
std::unique_ptr<SadTab> SadTab::Create(content::WebContents* web_contents,
SadTabKind kind) {
return std::make_unique<SadTabView>(web_contents, kind);
}
BEGIN_METADATA(SadTabView)
END_METADATA