blob: e6e2b3e51f1906d5e81eef50d7470002a463d8cf [file] [log] [blame]
// Copyright 2022 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/web_applications/commands/web_app_uninstall_command.h"
#include <memory>
#include <sstream>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/isolation_prefs_utils.h"
#include "chrome/browser/web_applications/locks/full_system_lock.h"
#include "chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_uninstall_job.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/uninstall_result_code.h"
namespace web_app {
namespace {
bool CanUninstallAllManagementSources(
const webapps::WebappUninstallSource& uninstall_source) {
// Check that the source was from a known 'user' or allowed ones such
// as kMigration.
return uninstall_source == webapps::WebappUninstallSource::kUnknown ||
uninstall_source == webapps::WebappUninstallSource::kAppMenu ||
uninstall_source == webapps::WebappUninstallSource::kAppsPage ||
uninstall_source == webapps::WebappUninstallSource::kOsSettings ||
uninstall_source == webapps::WebappUninstallSource::kAppManagement ||
uninstall_source == webapps::WebappUninstallSource::kMigration ||
uninstall_source == webapps::WebappUninstallSource::kAppList ||
uninstall_source == webapps::WebappUninstallSource::kShelf ||
uninstall_source == webapps::WebappUninstallSource::kSync ||
uninstall_source == webapps::WebappUninstallSource::kStartupCleanup ||
uninstall_source == webapps::WebappUninstallSource::kTestCleanup;
}
auto StreamableToString = [](const auto& value) {
std::stringstream ss;
ss << value;
return ss.str();
};
} // namespace
WebAppUninstallCommand::WebAppUninstallCommand(
const AppId& app_id,
absl::optional<WebAppManagement::Type> management_type_or_all,
webapps::WebappUninstallSource uninstall_source,
UninstallWebAppCallback callback,
Profile* profile)
: WebAppCommandTemplate<FullSystemLock>("WebAppUninstallCommand"),
lock_description_(std::make_unique<FullSystemLockDescription>()),
app_id_(app_id),
callback_(std::move(callback)),
profile_prefs_(profile->GetPrefs()) {
// Initializing data for uninstallation tracking.
queued_uninstalls_.emplace_back(app_id_, management_type_or_all,
uninstall_source);
webapps::InstallableMetrics::TrackUninstallEvent(uninstall_source);
}
WebAppUninstallCommand::~WebAppUninstallCommand() = default;
void WebAppUninstallCommand::StartWithLock(
std::unique_ptr<FullSystemLock> lock) {
lock_ = std::move(lock);
while (!queued_uninstalls_.empty()) {
DCHECK(!all_uninstalled_queued_);
const UninstallInfo current_uninstall =
std::move(queued_uninstalls_.back());
queued_uninstalls_.pop_back();
AppendUninstallInfoToDebugLog(current_uninstall);
const AppId& app_id = current_uninstall.app_id;
const WebApp* app = lock_->registrar().GetAppById(app_id);
if (!app) {
uninstall_results_[app_id] =
webapps::UninstallResultCode::kNoAppToUninstall;
AppendUninstallResultsToDebugLog(app_id);
continue;
}
// This contains the external uninstall logic.
if (current_uninstall.management_type_or_all.has_value()) {
const WebAppManagement::Type source =
current_uninstall.management_type_or_all.value();
// If there is more than a single source, then we can just remove the
// source from the web_app DB. Else we end up calling Uninstall() at the
// end.
if (!app->HasOnlySource(source)) {
// There is a chance that removed source type is NOT user uninstallable
// but the remaining source (after removal) types are user
// uninstallable. In this case, the following call will register os
// uninstallation.
apps_pending_uninstall_[app_id] = nullptr;
MaybeRegisterOsUninstall(
app, source, lock_->os_integration_manager(),
base::BindOnce(&WebAppUninstallCommand::
RemoveManagementTypeAfterOsUninstallRegistration,
weak_factory_.GetWeakPtr(), app_id, source,
current_uninstall.uninstall_source));
} else {
Uninstall(app_id, current_uninstall.uninstall_source);
}
} else {
// This contains the user uninstall and sync uninstall logic.
DCHECK(
CanUninstallAllManagementSources(current_uninstall.uninstall_source));
// The following DCHECK streamlines the user uninstall and sync uninstall
// flow, because for sync uninstalls, the web_app source is removed before
// being synced, so the first condition fails by the time an Uninstall is
// invoked.
DCHECK(app->CanUserUninstallWebApp() ||
current_uninstall.uninstall_source ==
webapps::WebappUninstallSource::kSync);
if (app->IsPreinstalledApp()) {
// Update the default uninstalled web_app prefs if it is a preinstalled
// app but being removed by user.
const WebApp::ExternalConfigMap& config_map =
app->management_to_external_config_map();
auto it = config_map.find(WebAppManagement::kDefault);
if (it != config_map.end()) {
UserUninstalledPreinstalledWebAppPrefs(profile_prefs_)
.Add(app_id, it->second.install_urls);
} else {
base::UmaHistogramBoolean(
"WebApp.Preinstalled.ExternalConfigMapAbsentDuringUninstall",
true);
}
}
Uninstall(app_id, current_uninstall.uninstall_source);
}
}
all_uninstalled_queued_ = true;
MaybeFinishUninstallAndDestruct();
}
void WebAppUninstallCommand::OnSyncSourceRemoved() {
// TODO(crbug.com/1320086): remove after uninstall from sync is async.
Abort(webapps::UninstallResultCode::kNoAppToUninstall);
return;
}
void WebAppUninstallCommand::OnShutdown() {
Abort(webapps::UninstallResultCode::kError);
return;
}
LockDescription& WebAppUninstallCommand::lock_description() const {
return *lock_description_;
}
base::Value WebAppUninstallCommand::ToDebugValue() const {
base::Value::Dict uninstall_info;
uninstall_info.Set("command_data", debug_log_.Clone());
return base::Value(std::move(uninstall_info));
}
void WebAppUninstallCommand::SetRemoveManagementTypeCallbackForTesting(
RemoveManagementTypeCallback callback) {
management_type_removed_callback_for_testing_ = std::move(callback);
}
WebAppUninstallCommand::UninstallInfo::UninstallInfo(
AppId app_id,
absl::optional<WebAppManagement::Type> management_type_or_all,
webapps::WebappUninstallSource uninstall_source)
: app_id(app_id),
management_type_or_all(management_type_or_all),
uninstall_source(uninstall_source) {}
WebAppUninstallCommand::UninstallInfo::~UninstallInfo() = default;
WebAppUninstallCommand::UninstallInfo::UninstallInfo(
const UninstallInfo& uninstall_info) = default;
WebAppUninstallCommand::UninstallInfo::UninstallInfo(
UninstallInfo&& uninstall_info) = default;
void WebAppUninstallCommand::AppendUninstallInfoToDebugLog(
const UninstallInfo& uninstall_info) {
base::Value::Dict source_info;
if (uninstall_info.management_type_or_all.has_value()) {
source_info.Set(
"management_type",
StreamableToString(uninstall_info.management_type_or_all.value()));
}
source_info.Set("uninstall_source", ConvertUninstallSourceToStringType(
uninstall_info.uninstall_source));
debug_log_.Set(uninstall_info.app_id, base::Value(std::move(source_info)));
}
void WebAppUninstallCommand::AppendUninstallResultsToDebugLog(
const AppId& app_id) {
base::Value::Dict* app_dict = debug_log_.FindDict(app_id);
DCHECK(app_dict);
app_dict->Set("uninstall_result", webapps::ConvertUninstallResultCodeToString(
uninstall_results_[app_id]));
}
void WebAppUninstallCommand::Abort(webapps::UninstallResultCode code) {
base::UmaHistogramBoolean("WebApp.Uninstall.Result",
(code == webapps::UninstallResultCode::kSuccess));
if (!callback_)
return;
SignalCompletionAndSelfDestruct(CommandResult::kFailure,
base::BindOnce(std::move(callback_), code));
}
void WebAppUninstallCommand::Uninstall(
const AppId& app_id,
const webapps::WebappUninstallSource& uninstall_source) {
QueueSubAppsForUninstallIfAny(app_id);
auto uninstall_job = WebAppUninstallJob::CreateAndStart(
app_id,
url::Origin::Create(lock_->registrar().GetAppById(app_id)->start_url()),
base::BindOnce(&WebAppUninstallCommand::OnSingleUninstallComplete,
weak_factory_.GetWeakPtr(), app_id, uninstall_source),
lock_->os_integration_manager(), lock_->sync_bridge(),
lock_->icon_manager(), lock_->registrar(), lock_->install_manager(),
lock_->translation_manager(), *profile_prefs_);
apps_pending_uninstall_[app_id] = std::move(uninstall_job);
}
void WebAppUninstallCommand::QueueSubAppsForUninstallIfAny(
const AppId& app_id) {
std::vector<AppId> sub_app_ids = lock_->registrar().GetAllSubAppIds(app_id);
for (const AppId& sub_app_id : sub_app_ids) {
queued_uninstalls_.emplace_back(
sub_app_id, WebAppManagement::kSubApp,
webapps::WebappUninstallSource::kParentUninstall);
}
}
void WebAppUninstallCommand::RemoveManagementTypeAfterOsUninstallRegistration(
const AppId& app_id,
const WebAppManagement::Type& management_type,
const webapps::WebappUninstallSource& uninstall_source,
OsHooksErrors os_hooks_errors) {
{
ScopedRegistryUpdate update(&lock_->sync_bridge());
WebApp* app_to_update = update->UpdateApp(app_id);
app_to_update->RemoveSource(management_type);
if (management_type == WebAppManagement::kSubApp) {
app_to_update->SetParentAppId(absl::nullopt);
}
}
if (management_type_removed_callback_for_testing_)
std::move(management_type_removed_callback_for_testing_).Run(app_id);
// Registering an OS uninstall is also an "uninstall", so the
// state is updated for the command.
OnSingleUninstallComplete(app_id, uninstall_source,
webapps::UninstallResultCode::kSuccess);
}
void WebAppUninstallCommand::OnSingleUninstallComplete(
const AppId& app_id,
const webapps::WebappUninstallSource& source,
webapps::UninstallResultCode code) {
DCHECK(base::Contains(apps_pending_uninstall_, app_id));
apps_pending_uninstall_.erase(app_id);
if (source == webapps::WebappUninstallSource::kSync) {
base::UmaHistogramBoolean("Webapp.SyncInitiatedUninstallResult",
code == webapps::UninstallResultCode::kSuccess);
}
uninstall_results_[app_id] = code;
AppendUninstallResultsToDebugLog(app_id);
MaybeFinishUninstallAndDestruct();
}
void WebAppUninstallCommand::MaybeFinishUninstallAndDestruct() {
if (apps_pending_uninstall_.empty() && all_uninstalled_queued_) {
// All uninstall jobs have finished.
SignalCompletionAndSelfDestruct(
CommandResult::kSuccess,
base::BindOnce(std::move(callback_), uninstall_results_[app_id_]));
}
}
} // namespace web_app