blob: 660b39e198efba797bbee708a904147d5f55c78e [file] [log] [blame]
// Copyright (c) 2019 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/apps/app_dialog/app_uninstall_dialog_view.h"
#include <string>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/google/core/common/google_util.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/elide_url.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/manifest_url_handlers.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#endif
namespace {
AppUninstallDialogView* g_app_uninstall_dialog_view = nullptr;
#if BUILDFLAG(IS_CHROMEOS_ASH)
bool IsArcShortcutApp(Profile* profile, const std::string& app_id) {
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
DCHECK(arc_prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id);
DCHECK(app_info);
return app_info->shortcut;
}
#endif
std::u16string GetWindowTitleForApp(Profile* profile,
apps::mojom::AppType app_type,
const std::string& app_id,
const std::string& app_name) {
using apps::mojom::AppType;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// On ChromeOS, all app types exist, but Arc shortcut apps get the regular
// extension uninstall title.
if (app_type == AppType::kArc && IsArcShortcutApp(profile, app_id))
return l10n_util::GetStringUTF16(IDS_EXTENSION_UNINSTALL_PROMPT_TITLE);
#else
// On non-ChromeOS, only extension and web app types meaningfully exist.
DCHECK(app_type != AppType::kExtension && app_type != AppType::kWeb);
#endif
return l10n_util::GetStringFUTF16(IDS_PROMPT_APP_UNINSTALL_TITLE,
base::UTF8ToUTF16(app_name));
}
} // namespace
// static
void apps::UninstallDialog::UiBase::Create(
Profile* profile,
apps::mojom::AppType app_type,
const std::string& app_id,
const std::string& app_name,
gfx::ImageSkia image,
gfx::NativeWindow parent_window,
apps::UninstallDialog* uninstall_dialog) {
constrained_window::CreateBrowserModalDialogViews(
(new AppUninstallDialogView(profile, app_type, app_id, app_name, image,
uninstall_dialog)),
parent_window)
->Show();
}
AppUninstallDialogView::AppUninstallDialogView(
Profile* profile,
apps::mojom::AppType app_type,
const std::string& app_id,
const std::string& app_name,
gfx::ImageSkia image,
apps::UninstallDialog* uninstall_dialog)
: apps::UninstallDialog::UiBase(uninstall_dialog),
AppDialogView(image),
profile_(profile) {
SetModalType(ui::MODAL_TYPE_WINDOW);
SetTitle(GetWindowTitleForApp(profile, app_type, app_id, app_name));
SetCloseCallback(base::BindOnce(&AppUninstallDialogView::OnDialogCancelled,
base::Unretained(this)));
SetCancelCallback(base::BindOnce(&AppUninstallDialogView::OnDialogCancelled,
base::Unretained(this)));
SetAcceptCallback(base::BindOnce(&AppUninstallDialogView::OnDialogAccepted,
base::Unretained(this)));
InitializeView(profile, app_type, app_id, app_name);
chrome::RecordDialogCreation(chrome::DialogIdentifier::APP_UNINSTALL);
g_app_uninstall_dialog_view = this;
}
AppUninstallDialogView::~AppUninstallDialogView() {
g_app_uninstall_dialog_view = nullptr;
}
// static
AppUninstallDialogView* AppUninstallDialogView::GetActiveViewForTesting() {
return g_app_uninstall_dialog_view;
}
void AppUninstallDialogView::InitializeView(Profile* profile,
apps::mojom::AppType app_type,
const std::string& app_id,
const std::string& app_name) {
SetButtonLabel(
ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_UNINSTALL_APP_BUTTON));
ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
switch (app_type) {
case apps::mojom::AppType::kUnknown:
case apps::mojom::AppType::kBuiltIn:
case apps::mojom::AppType::kMacOs:
case apps::mojom::AppType::kStandaloneBrowser:
case apps::mojom::AppType::kRemote:
NOTREACHED();
break;
case apps::mojom::AppType::kArc:
#if BUILDFLAG(IS_CHROMEOS_ASH)
InitializeViewForArcApp(profile, app_id);
#else
NOTREACHED();
#endif
break;
case apps::mojom::AppType::kPluginVm:
#if BUILDFLAG(IS_CHROMEOS_ASH)
InitializeViewWithMessage(l10n_util::GetStringFUTF16(
IDS_PLUGIN_VM_UNINSTALL_PROMPT_BODY, base::UTF8ToUTF16(app_name)));
#else
NOTREACHED();
#endif
break;
case apps::mojom::AppType::kBorealis:
// TODO(b/178741230): Borealis' uninstaller needs custom text. For now
// just use Crostini's.
case apps::mojom::AppType::kCrostini:
#if BUILDFLAG(IS_CHROMEOS_ASH)
InitializeViewWithMessage(l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_CONFIRM_BODY));
#else
NOTREACHED();
#endif
break;
case apps::mojom::AppType::kWeb:
case apps::mojom::AppType::kSystemWeb:
InitializeViewForWebApp(profile, app_id);
break;
case apps::mojom::AppType::kExtension:
InitializeViewForExtension(profile, app_id);
break;
}
}
void AppUninstallDialogView::InitializeCheckbox(const GURL& app_start_url) {
std::vector<std::u16string> replacements;
replacements.push_back(url_formatter::FormatUrlForSecurityDisplay(
app_start_url, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
const bool is_google = google_util::IsGoogleHostname(
app_start_url.host_piece(), google_util::ALLOW_SUBDOMAIN);
if (!is_google) {
auto domain = net::registry_controlled_domains::GetDomainAndRegistry(
app_start_url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (!domain.empty())
domain[0] = base::ToUpperASCII(domain[0]);
replacements.push_back(base::ASCIIToUTF16(domain));
}
std::u16string learn_more_text =
l10n_util::GetStringUTF16(IDS_APP_UNINSTALL_PROMPT_LEARN_MORE);
replacements.push_back(learn_more_text);
auto checkbox_label = std::make_unique<views::StyledLabel>();
std::vector<size_t> offsets;
checkbox_label->SetText(l10n_util::GetStringFUTF16(
is_google ? IDS_APP_UNINSTALL_PROMPT_REMOVE_DATA_CHECKBOX_FOR_GOOGLE
: IDS_APP_UNINSTALL_PROMPT_REMOVE_DATA_CHECKBOX_FOR_NON_GOOGLE,
replacements, &offsets));
DCHECK_EQ(replacements.size(), offsets.size());
const size_t offset = offsets.back();
checkbox_label->AddStyleRange(
gfx::Range(offset, offset + learn_more_text.length()),
views::StyledLabel::RangeStyleInfo::CreateForLink(base::BindRepeating(
[](Profile* profile) {
NavigateParams params(
profile,
GURL("https://support.google.com/chromebook/?p=uninstallpwa"),
ui::PAGE_TRANSITION_LINK);
Navigate(&params);
},
profile_)));
views::StyledLabel::RangeStyleInfo checkbox_style;
checkbox_style.text_style = views::style::STYLE_PRIMARY;
gfx::Range before_link_range(0, offset);
checkbox_label->AddStyleRange(before_link_range, checkbox_style);
// Shift the text down to align with the checkbox.
checkbox_label->SetBorder(views::CreateEmptyBorder(3, 0, 0, 0));
auto clear_site_data_checkbox =
std::make_unique<views::Checkbox>(std::u16string());
clear_site_data_checkbox->SetAssociatedLabel(checkbox_label.get());
// Create a view to hold the checkbox and the text.
auto checkbox_view = std::make_unique<views::View>();
views::GridLayout* checkbox_layout =
checkbox_view->SetLayoutManager(std::make_unique<views::GridLayout>());
const int kReportColumnSetId = 0;
views::ColumnSet* cs = checkbox_layout->AddColumnSet(kReportColumnSetId);
cs->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING,
views::GridLayout::kFixedSize,
views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
cs->AddPaddingColumn(views::GridLayout::kFixedSize,
ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_LABEL_HORIZONTAL));
cs->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0,
views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
checkbox_layout->StartRow(views::GridLayout::kFixedSize, kReportColumnSetId);
clear_site_data_checkbox_ =
checkbox_layout->AddView(std::move(clear_site_data_checkbox));
checkbox_layout->AddView(std::move(checkbox_label));
AddChildView(std::move(checkbox_view));
}
void AppUninstallDialogView::InitializeViewForExtension(
Profile* profile,
const std::string& app_id) {
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
app_id);
DCHECK(extension);
extensions::ExtensionManagement* extension_management =
extensions::ExtensionManagementFactory::GetForBrowserContext(profile);
if (extension_management->UpdatesFromWebstore(*extension)) {
auto report_abuse_checkbox = std::make_unique<views::Checkbox>(
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_UNINSTALL_REPORT_ABUSE));
report_abuse_checkbox->SetMultiLine(true);
report_abuse_checkbox_ = AddChildView(std::move(report_abuse_checkbox));
} else if (extension->from_bookmark()) {
InitializeCheckbox(extensions::AppLaunchInfo::GetFullLaunchURL(extension));
}
}
void AppUninstallDialogView::InitializeViewForWebApp(
Profile* profile,
const std::string& app_id) {
auto* provider = web_app::WebAppProvider::Get(profile);
DCHECK(provider);
GURL app_start_url = provider->registrar().GetAppStartUrl(app_id);
DCHECK(app_start_url.is_valid());
InitializeCheckbox(app_start_url);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
void AppUninstallDialogView::InitializeViewForArcApp(
Profile* profile,
const std::string& app_id) {
if (IsArcShortcutApp(profile, app_id)) {
SetButtonLabel(
ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON));
} else {
InitializeViewWithMessage(l10n_util::GetStringUTF16(
IDS_ARC_APP_UNINSTALL_PROMPT_DATA_REMOVAL_WARNING));
}
}
void AppUninstallDialogView::InitializeViewWithMessage(
const std::u16string& message) {
auto* label = AddChildView(std::make_unique<views::Label>(message));
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetAllowCharacterBreak(true);
}
#endif
void AppUninstallDialogView::OnDialogCancelled() {
uninstall_dialog()->OnDialogClosed(false /* uninstall */,
false /* clear_site_data */,
false /* report_abuse */);
}
void AppUninstallDialogView::OnDialogAccepted() {
const bool clear_site_data =
clear_site_data_checkbox_ && clear_site_data_checkbox_->GetChecked();
const bool report_abuse_checkbox =
report_abuse_checkbox_ && report_abuse_checkbox_->GetChecked();
uninstall_dialog()->OnDialogClosed(true /* uninstall */, clear_site_data,
report_abuse_checkbox);
}