blob: 675fce12920aabe0e261f6e7b4906449d5b43fc0 [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/externally_managed_install_command.h"
#include <memory>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/commands/web_app_command.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/install_bounce_metric.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/locks/noop_lock.h"
#include "chrome/browser/web_applications/locks/web_app_lock_manager.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_data_retriever.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_logging.h"
#include "chrome/browser/web_applications/web_app_uninstall_and_replace_job.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "content/public/browser/web_contents.h"
namespace web_app {
ExternallyManagedInstallCommand::ExternallyManagedInstallCommand(
Profile* profile,
const ExternalInstallOptions& external_install_options,
InstallAndReplaceCallback callback,
base::WeakPtr<content::WebContents> contents,
std::unique_ptr<WebAppDataRetriever> data_retriever)
: WebAppCommandTemplate<NoopLock>("ExternallyManagedInstallCommand"),
profile_(profile),
noop_lock_description_(std::make_unique<NoopLockDescription>()),
install_params_(
ConvertExternalInstallOptionsToParams(external_install_options)),
install_surface_(ConvertExternalInstallSourceToInstallSource(
external_install_options.install_source)),
apps_to_uninstall_(external_install_options.uninstall_and_replace),
install_callback_(std::move(callback)),
web_contents_(contents),
data_retriever_(std::move(data_retriever)),
install_error_log_entry_(/*background_installation=*/true,
install_surface_) {
debug_value_.Set("external_install_options",
external_install_options.AsDebugValue());
if (!install_params_.locally_installed) {
DCHECK(!install_params_.add_to_applications_menu);
DCHECK(!install_params_.add_to_desktop);
DCHECK(!install_params_.add_to_quick_launch_bar);
}
DCHECK_NE(install_surface_, webapps::WebappInstallSource::SYNC);
}
ExternallyManagedInstallCommand::~ExternallyManagedInstallCommand() = default;
LockDescription& ExternallyManagedInstallCommand::lock_description() const {
DCHECK(noop_lock_description_ || app_lock_description_);
if (app_lock_description_)
return *app_lock_description_;
return *noop_lock_description_;
}
void ExternallyManagedInstallCommand::StartWithLock(
std::unique_ptr<NoopLock> lock) {
noop_lock_ = std::move(lock);
if (!web_contents_ || web_contents_->IsBeingDestroyed()) {
Abort(webapps::InstallResultCode::kWebContentsDestroyed);
return;
}
data_retriever_->GetWebAppInstallInfo(
web_contents_.get(),
base::BindOnce(
&ExternallyManagedInstallCommand::OnGetWebAppInstallInfoInCommand,
weak_factory_.GetWeakPtr()));
}
void ExternallyManagedInstallCommand::OnSyncSourceRemoved() {}
void ExternallyManagedInstallCommand::OnShutdown() {
Abort(webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown);
}
base::Value ExternallyManagedInstallCommand::ToDebugValue() const {
base::Value::Dict value(debug_value_.Clone());
value.Set("app_id", app_id_);
return base::Value(std::move(value));
}
void ExternallyManagedInstallCommand::Abort(webapps::InstallResultCode code) {
if (!install_callback_)
return;
debug_value_.Set("result_code", base::StreamableToString(code));
webapps::InstallableMetrics::TrackInstallResult(false);
SignalCompletionAndSelfDestruct(
CommandResult::kFailure,
base::BindOnce(std::move(install_callback_), AppId(), code,
/*did_uninstall_and_replace=*/false));
}
void ExternallyManagedInstallCommand::OnGetWebAppInstallInfoInCommand(
std::unique_ptr<WebAppInstallInfo> web_app_info) {
web_app_info_ = std::move(web_app_info);
if (!web_contents_ || web_contents_->IsBeingDestroyed()) {
Abort(webapps::InstallResultCode::kWebContentsDestroyed);
return;
}
if (!web_app_info_) {
Abort(webapps::InstallResultCode::kGetWebAppInstallInfoFailed);
return;
}
// Write values from install_params_ to web_app_info.
bypass_service_worker_check_ = install_params_.bypass_service_worker_check;
// Set start_url to fallback_start_url as web_contents may have been
// redirected. Will be overridden by manifest values if present.
DCHECK(install_params_.fallback_start_url.is_valid());
web_app_info_->start_url = install_params_.fallback_start_url;
if (install_params_.fallback_app_name.has_value())
web_app_info_->title = install_params_.fallback_app_name.value();
ApplyParamsToWebAppInstallInfo(install_params_, *web_app_info_);
data_retriever_->CheckInstallabilityAndRetrieveManifest(
web_contents_.get(), bypass_service_worker_check_,
base::BindOnce(
&ExternallyManagedInstallCommand::OnDidPerformInstallableCheck,
weak_factory_.GetWeakPtr()));
}
void ExternallyManagedInstallCommand::OnDidPerformInstallableCheck(
blink::mojom::ManifestPtr opt_manifest,
const GURL& manifest_url,
bool valid_manifest_for_web_app,
webapps::InstallableStatusCode error_code) {
if (!web_contents_ || web_contents_->IsBeingDestroyed()) {
Abort(webapps::InstallResultCode::kWebContentsDestroyed);
return;
}
if (install_params_.require_manifest && !valid_manifest_for_web_app) {
LOG(WARNING) << "Did not install " << manifest_url.spec()
<< " because it didn't have a manifest for web app";
Abort(webapps::InstallResultCode::kNotValidManifestForWebApp);
return;
}
// A system app should always have a manifest icon.
if (install_surface_ == webapps::WebappInstallSource::SYSTEM_DEFAULT) {
DCHECK(opt_manifest);
DCHECK(!opt_manifest->icons.empty());
}
debug_value_.Set("had_manifest", false);
if (opt_manifest) {
debug_value_.Set("had_manifest", true);
UpdateWebAppInfoFromManifest(*opt_manifest, manifest_url,
web_app_info_.get());
}
if (install_params_.install_as_shortcut) {
*web_app_info_ = WebAppInstallInfo::CreateInstallInfoForCreateShortcut(
web_contents_->GetLastCommittedURL(), *web_app_info_);
}
app_id_ = GenerateAppId(web_app_info_->manifest_id, web_app_info_->start_url);
// If the manifest specified icons, don't use the page icons.
const bool skip_page_favicons = opt_manifest && !opt_manifest->icons.empty();
base::flat_set<GURL> icon_urls = GetValidIconUrlsToDownload(*web_app_info_);
data_retriever_->GetIcons(
web_contents_.get(), std::move(icon_urls), skip_page_favicons,
base::BindOnce(&ExternallyManagedInstallCommand::
OnIconsRetrievedUpgradeLockDescription,
weak_factory_.GetWeakPtr()));
}
void ExternallyManagedInstallCommand::OnIconsRetrievedUpgradeLockDescription(
IconsDownloadedResult result,
IconsMap icons_map,
DownloadedIconsHttpResults icons_http_results) {
if (!web_contents_ || web_contents_->IsBeingDestroyed()) {
Abort(webapps::InstallResultCode::kWebContentsDestroyed);
return;
}
PopulateProductIcons(web_app_info_.get(), &icons_map);
PopulateOtherIcons(web_app_info_.get(), icons_map);
RecordDownloadedIconsResultAndHttpStatusCodes(result, icons_http_results);
install_error_log_entry_.LogDownloadedIconsErrors(
*web_app_info_, result, icons_map, icons_http_results);
if (result != IconsDownloadedResult::kCompleted) {
icon_download_failed_ = true;
}
app_lock_description_ =
command_manager()->lock_manager().UpgradeAndAcquireLock(
std::move(noop_lock_), {app_id_},
base::BindOnce(
&ExternallyManagedInstallCommand::OnLockUpgradedFinalizeInstall,
weak_factory_.GetWeakPtr()));
}
void ExternallyManagedInstallCommand::OnLockUpgradedFinalizeInstall(
std::unique_ptr<AppLock> app_lock) {
app_lock_ = std::move(app_lock);
if (on_lock_upgraded_callback_for_testing_)
std::move(on_lock_upgraded_callback_for_testing_).Run();
if (!web_contents_ || web_contents_->IsBeingDestroyed()) {
Abort(webapps::InstallResultCode::kWebContentsDestroyed);
return;
}
WebAppInstallFinalizer::FinalizeOptions finalize_options(install_surface_);
finalize_options.locally_installed = install_params_.locally_installed;
finalize_options.overwrite_existing_manifest_fields =
install_params_.force_reinstall;
finalize_options.parent_app_id = install_params_.parent_app_id;
ApplyParamsToFinalizeOptions(install_params_, finalize_options);
if (install_params_.user_display_mode.has_value())
web_app_info_->user_display_mode = install_params_.user_display_mode;
finalize_options.add_to_applications_menu =
install_params_.add_to_applications_menu;
finalize_options.add_to_desktop = install_params_.add_to_desktop;
finalize_options.add_to_quick_launch_bar =
install_params_.add_to_quick_launch_bar;
if (app_lock_->registrar().IsInstalled(app_id_)) {
// If an installation is triggered for the same app but with a
// different install_url, then we overwrite the manifest fields.
// If icon downloads fail, then we would not overwrite the icon
// in the web_app DB.
finalize_options.overwrite_existing_manifest_fields = true;
finalize_options.skip_icon_writes_on_download_failure =
icon_download_failed_;
}
app_lock_->install_finalizer().FinalizeInstall(
*web_app_info_, finalize_options,
base::BindOnce(&ExternallyManagedInstallCommand::OnInstallFinalized,
weak_factory_.GetWeakPtr()));
// Check that the finalizer hasn't called OnInstallFinalizedMaybeReparentTab
// synchronously:
DCHECK(install_callback_);
}
void ExternallyManagedInstallCommand::OnInstallFinalized(
const AppId& app_id,
webapps::InstallResultCode code,
OsHooksErrors os_hooks_errors) {
if (!web_contents_ || web_contents_->IsBeingDestroyed()) {
Abort(webapps::InstallResultCode::kWebContentsDestroyed);
return;
}
if (code != webapps::InstallResultCode::kSuccessNewInstall) {
Abort(code);
return;
}
debug_value_.Set("result_code", base::StreamableToString(code));
RecordWebAppInstallationTimestamp(
Profile::FromBrowserContext(web_contents_->GetBrowserContext())
->GetPrefs(),
app_id, install_surface_);
if (install_params_.locally_installed) {
RecordAppBanner(web_contents_.get(), web_app_info_->start_url);
}
if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) {
base::Value task_error_dict = install_error_log_entry_.TakeErrorDict();
if (!task_error_dict.DictEmpty())
command_manager()->LogToInstallManager(std::move(task_error_dict));
}
webapps::InstallableMetrics::TrackInstallResult(webapps::IsSuccess(code));
DCHECK(app_lock_);
uninstall_and_replace_job_.emplace(
profile_, app_lock_->AsWeakPtr(), apps_to_uninstall_, app_id,
base::BindOnce(&ExternallyManagedInstallCommand::OnUninstallAndReplaced,
weak_factory_.GetWeakPtr(), app_id, code));
uninstall_and_replace_job_->Start();
}
void ExternallyManagedInstallCommand::OnUninstallAndReplaced(
const AppId& app_id,
webapps::InstallResultCode code,
bool did_uninstall_and_replace) {
debug_value_.Set("did_uninstall_and_replace", did_uninstall_and_replace);
SignalCompletionAndSelfDestruct(
CommandResult::kSuccess,
base::BindOnce(std::move(install_callback_), app_id, code,
did_uninstall_and_replace));
}
void ExternallyManagedInstallCommand::SetOnLockUpgradedCallbackForTesting(
base::OnceClosure callback) {
on_lock_upgraded_callback_for_testing_ = std::move(callback);
}
} // namespace web_app