blob: 6aa331417a4f49407b18b2840f28d7fc1dab35a9 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/security_state_tab_helper.h"
#include "chrome/browser/web_applications/install_bounce_metric.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.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_icon_generator.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_task.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_url_loader.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "net/http/http_status_code.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_data.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "net/base/url_util.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/crosapi/mojom/arc.mojom.h"
#include "chromeos/crosapi/mojom/web_app_service.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "chromeos/startup/browser_params_proxy.h"
#endif
namespace web_app {
namespace {
#if BUILDFLAG(IS_CHROMEOS)
const char kChromeOsPlayPlatform[] = "chromeos_play";
const char kPlayIntentPrefix[] =
"https://play.google.com/store/apps/details?id=";
const char kPlayStorePackage[] = "com.android.vending";
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr bool kAddAppsToQuickLaunchBarByDefault = false;
#else
constexpr bool kAddAppsToQuickLaunchBarByDefault = true;
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS)
struct PlayStoreIntent {
std::string app_id;
std::string intent;
};
// Find the first Chrome OS app in related_applications of |manifest| and return
// the details necessary to redirect the user to the app's listing in the Play
// Store.
absl::optional<PlayStoreIntent> GetPlayStoreIntentFromManifest(
const blink::mojom::Manifest& manifest) {
for (const auto& app : manifest.related_applications) {
std::string id = base::UTF16ToUTF8(app.id.value_or(std::u16string()));
if (!base::EqualsASCII(app.platform.value_or(std::u16string()),
kChromeOsPlayPlatform)) {
continue;
}
if (id.empty()) {
// Fallback to ID in the URL.
if (!net::GetValueForKeyInQuery(app.url, "id", &id) || id.empty()) {
continue;
}
}
std::string referrer;
if (net::GetValueForKeyInQuery(app.url, "referrer", &referrer) &&
!referrer.empty()) {
referrer = "&referrer=" + referrer;
}
std::string intent = kPlayIntentPrefix + id + referrer;
return PlayStoreIntent{id, intent};
}
return absl::nullopt;
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
bool ShouldInteractWithArc() {
auto* lacros_service = chromeos::LacrosService::Get();
return lacros_service &&
// Check if the feature is enabled.
chromeos::BrowserParamsProxy::Get()->WebAppsEnabled() &&
// Only use ARC installation flow if we know that remote ash-chrome is
// capable of installing from Play Store in lacros-chrome, to avoid
// redirecting users to the Play Store if they cannot install
// anything.
lacros_service->IsAvailable<crosapi::mojom::WebAppService>();
}
mojo::Remote<crosapi::mojom::Arc>* GetArcRemoteWithMinVersion(
uint32_t minVersion) {
auto* lacros_service = chromeos::LacrosService::Get();
if (lacros_service && lacros_service->IsAvailable<crosapi::mojom::Arc>() &&
lacros_service->GetInterfaceVersion(crosapi::mojom::Arc::Uuid_) >=
static_cast<int>(minVersion)) {
return &lacros_service->GetRemote<crosapi::mojom::Arc>();
}
return nullptr;
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
} // namespace
WebAppInstallTask::WebAppInstallTask(
Profile* profile,
WebAppInstallFinalizer* install_finalizer,
std::unique_ptr<WebAppDataRetriever> data_retriever,
WebAppRegistrar* registrar,
webapps::WebappInstallSource install_surface)
: data_retriever_(std::move(data_retriever)),
install_finalizer_(install_finalizer),
profile_(profile),
registrar_(registrar),
install_surface_(install_surface),
log_entry_(/*background_installation=*/false, install_surface) {
DCHECK_NE(install_surface_, webapps::WebappInstallSource::SYNC);
// Note: background_installation in the log entry is updated later in the
// install method calls.
}
WebAppInstallTask::~WebAppInstallTask() {
// If this task is still observing a WebContents, then the callbacks haven't
// yet been run. Run them before the task is destroyed.
if (web_contents())
CallInstallCallback(AppId(),
webapps::InstallResultCode::kInstallTaskDestroyed);
}
void WebAppInstallTask::ExpectAppId(const AppId& expected_app_id) {
expected_app_id_ = expected_app_id;
}
void WebAppInstallTask::InstallWebAppFromManifest(
content::WebContents* contents,
bool bypass_service_worker_check,
WebAppInstallDialogCallback dialog_callback,
OnceInstallCallback install_callback) {
DCHECK(AreWebAppsUserInstallable(profile_));
CheckInstallPreconditions();
Observe(contents);
dialog_callback_ = std::move(dialog_callback);
install_callback_ = std::move(install_callback);
auto web_app_info = std::make_unique<WebAppInstallInfo>();
if (install_params_)
ApplyParamsToWebAppInstallInfo(*install_params_, *web_app_info);
data_retriever_->CheckInstallabilityAndRetrieveManifest(
web_contents(), bypass_service_worker_check,
base::BindOnce(&WebAppInstallTask::OnDidPerformInstallableCheck,
GetWeakPtr(), std::move(web_app_info)));
}
void WebAppInstallTask::InstallWebAppFromManifestWithFallback(
content::WebContents* contents,
WebAppInstallFlow flow,
WebAppInstallDialogCallback dialog_callback,
OnceInstallCallback install_callback) {
DCHECK(AreWebAppsUserInstallable(profile_));
CheckInstallPreconditions();
Observe(contents);
dialog_callback_ = std::move(dialog_callback);
install_callback_ = std::move(install_callback);
flow_ = flow;
data_retriever_->GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppInstallTask::OnGetWebAppInstallInfo, GetWeakPtr()));
}
// static
void WebAppInstallTask::UpdateFinalizerClientData(
const absl::optional<WebAppInstallParams>& params,
WebAppInstallFinalizer::FinalizeOptions* options) {
if (params) {
if (IsChromeOsDataMandatory()) {
options->chromeos_data.emplace();
options->chromeos_data->show_in_launcher =
params->add_to_applications_menu;
options->chromeos_data->show_in_search = params->add_to_search;
options->chromeos_data->show_in_management = params->add_to_management;
options->chromeos_data->is_disabled = params->is_disabled;
options->chromeos_data->oem_installed = params->oem_installed;
options->chromeos_data->handles_file_open_intents =
params->handles_file_open_intents;
}
options->bypass_os_hooks = params->bypass_os_hooks;
options->add_to_applications_menu = params->add_to_applications_menu;
options->add_to_desktop = params->add_to_desktop;
options->add_to_quick_launch_bar = params->add_to_quick_launch_bar;
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (params->system_app_type.has_value()) {
options->system_web_app_data.emplace();
options->system_web_app_data->system_app_type =
params->system_app_type.value();
}
#endif
}
}
void WebAppInstallTask::InstallWebAppFromInfo(
std::unique_ptr<WebAppInstallInfo> web_app_install_info,
bool overwrite_existing_manifest_fields,
OnceInstallCallback callback) {
CheckInstallPreconditions();
PopulateProductIcons(web_app_install_info.get(),
/*icons_map*/ nullptr);
// No IconsMap to populate shortcut item icons from.
if (install_params_)
ApplyParamsToWebAppInstallInfo(*install_params_, *web_app_install_info);
background_installation_ = true;
log_entry_.set_background_installation(true);
install_callback_ = std::move(callback);
RecordInstallEvent();
WebAppInstallFinalizer::FinalizeOptions options(install_surface_);
options.locally_installed = true;
options.overwrite_existing_manifest_fields =
overwrite_existing_manifest_fields;
if (install_params_) {
ApplyParamsToFinalizeOptions(*install_params_, options);
} else {
options.bypass_os_hooks = true;
}
install_finalizer_->FinalizeInstall(
*web_app_install_info, options,
base::BindOnce(&WebAppInstallTask::OnInstallFinalized, GetWeakPtr()));
}
void WebAppInstallTask::LoadAndRetrieveWebAppInstallInfoWithIcons(
const GURL& start_url,
WebAppUrlLoader* url_loader,
RetrieveWebAppInstallInfoWithIconsCallback callback) {
CheckInstallPreconditions();
retrieve_info_callback_ = std::move(callback);
background_installation_ = true;
log_entry_.set_background_installation(true);
only_retrieve_web_app_install_info_ = true;
web_contents_ = CreateWebContents(profile_);
Observe(web_contents_.get());
DCHECK(url_loader);
url_loader->LoadUrl(
start_url, web_contents(),
WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef,
base::BindOnce(&WebAppInstallTask::OnWebAppUrlLoadedGetWebAppInstallInfo,
GetWeakPtr(), start_url));
}
// static
std::unique_ptr<content::WebContents> WebAppInstallTask::CreateWebContents(
Profile* profile) {
std::unique_ptr<content::WebContents> web_contents =
content::WebContents::Create(content::WebContents::CreateParams(profile));
CreateWebAppInstallTabHelpers(web_contents.get());
return web_contents;
}
content::WebContents* WebAppInstallTask::GetInstallingWebContents() {
return web_contents();
}
base::WeakPtr<WebAppInstallTask> WebAppInstallTask::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void WebAppInstallTask::WebContentsDestroyed() {
CallInstallCallback(AppId(),
webapps::InstallResultCode::kWebContentsDestroyed);
}
base::Value::Dict WebAppInstallTask::TakeErrorDict() {
DCHECK(log_entry_.HasErrorDict());
return log_entry_.TakeErrorDict();
}
void WebAppInstallTask::CheckInstallPreconditions() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!profile_->ShutdownStarted());
// Concurrent calls are not allowed.
DCHECK(!web_contents());
CHECK(!install_callback_);
CHECK(!retrieve_info_callback_);
DCHECK(!initiated_);
initiated_ = true;
}
void WebAppInstallTask::RecordInstallEvent() {
if (webapps::InstallableMetrics::IsReportableInstallSource(
install_surface_)) {
webapps::InstallableMetrics::TrackInstallEvent(install_surface_);
}
}
void WebAppInstallTask::CallInstallCallback(const AppId& app_id,
webapps::InstallResultCode code) {
Observe(nullptr);
dialog_callback_.Reset();
if (only_retrieve_web_app_install_info_) {
DCHECK(retrieve_info_callback_);
if (web_app_install_info_) {
std::move(retrieve_info_callback_).Run(std::move(*web_app_install_info_));
web_app_install_info_ = absl::nullopt;
} else {
std::move(retrieve_info_callback_).Run(code);
}
return;
}
DCHECK(install_callback_);
webapps::InstallableMetrics::TrackInstallResult(webapps::IsSuccess(code));
std::move(install_callback_).Run(app_id, code);
}
bool WebAppInstallTask::ShouldStopInstall() const {
// Install should stop early if WebContents is being destroyed.
// WebAppInstallTask::WebContentsDestroyed will get called eventually and
// the callback will be invoked at that point.
return !web_contents() || web_contents()->IsBeingDestroyed() ||
profile_->ShutdownStarted();
}
void WebAppInstallTask::OnWebAppUrlLoadedGetWebAppInstallInfo(
const GURL& url_to_load,
WebAppUrlLoader::Result result) {
if (ShouldStopInstall())
return;
if (result != WebAppUrlLoader::Result::kUrlLoaded) {
log_entry_.LogUrlLoaderError("OnWebAppUrlLoaded", url_to_load.spec(),
result);
}
if (result == WebAppUrlLoader::Result::kRedirectedUrlLoaded) {
CallInstallCallback(expected_app_id_.value_or(AppId()),
webapps::InstallResultCode::kInstallURLRedirected);
return;
}
if (result == WebAppUrlLoader::Result::kFailedPageTookTooLong) {
CallInstallCallback(expected_app_id_.value_or(AppId()),
webapps::InstallResultCode::kInstallURLLoadTimeOut);
return;
}
if (result != WebAppUrlLoader::Result::kUrlLoaded) {
CallInstallCallback(expected_app_id_.value_or(AppId()),
webapps::InstallResultCode::kInstallURLLoadFailed);
return;
}
data_retriever_->GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppInstallTask::OnGetWebAppInstallInfo, GetWeakPtr()));
}
void WebAppInstallTask::OnGetWebAppInstallInfo(
std::unique_ptr<WebAppInstallInfo> web_app_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (ShouldStopInstall())
return;
if (!web_app_info) {
CallInstallCallback(
AppId(), webapps::InstallResultCode::kGetWebAppInstallInfoFailed);
return;
}
bool bypass_service_worker_check = false;
if (install_params_) {
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(), bypass_service_worker_check,
base::BindOnce(&WebAppInstallTask::OnDidPerformInstallableCheck,
GetWeakPtr(), std::move(web_app_info)));
}
void WebAppInstallTask::OnDidPerformInstallableCheck(
std::unique_ptr<WebAppInstallInfo> web_app_info,
blink::mojom::ManifestPtr opt_manifest,
const GURL& manifest_url,
bool valid_manifest_for_web_app,
webapps::InstallableStatusCode error_code) {
if (ShouldStopInstall())
return;
DCHECK(web_app_info);
if (install_params_ && install_params_->require_manifest &&
!valid_manifest_for_web_app) {
LOG(WARNING) << "Did not install " << web_app_info->start_url.spec()
<< " because it didn't have a manifest for web app";
CallInstallCallback(AppId(),
webapps::InstallResultCode::kNotValidManifestForWebApp);
return;
}
if (opt_manifest)
UpdateWebAppInfoFromManifest(*opt_manifest, manifest_url,
web_app_info.get());
if (flow_ == WebAppInstallFlow::kCreateShortcut &&
base::FeatureList::IsEnabled(
webapps::features::kCreateShortcutIgnoresManifest)) {
// When creating a shortcut, the |manifest_id| is not part of the App's
// primary key. The only thing that identifies a shortcut is the start URL,
// which is always set to the current page.
*web_app_info = WebAppInstallInfo::CreateInstallInfoForCreateShortcut(
web_contents()->GetLastCommittedURL(), *web_app_info);
}
AppId app_id =
GenerateAppId(web_app_info->manifest_id, web_app_info->start_url);
// Does the app_id expectation check if requested.
if (expected_app_id_.has_value() && *expected_app_id_ != app_id) {
log_entry_.LogExpectedAppIdError("OnDidPerformInstallableCheck",
web_app_info->start_url.spec(), app_id,
expected_app_id_.value());
CallInstallCallback(*expected_app_id_,
webapps::InstallResultCode::kExpectedAppIdCheckFailed);
return;
}
// Duplicate installation check for SUB_APP installs (done here since the
// AppId isn't available beforehand). It's possible that the app was already
// installed, but from a different source (eg. by the user manually). In that
// case we proceed with the installation which adds the SUB_APP install source
// as well.
if (install_surface_ == webapps::WebappInstallSource::SUB_APP) {
DCHECK(install_params_ && install_params_->parent_app_id.has_value());
if (registrar_->WasInstalledBySubApp(app_id)) {
CallInstallCallback(std::move(app_id),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
return;
}
}
base::flat_set<GURL> icon_urls = GetValidIconUrlsToDownload(*web_app_info);
// A system app should always have a manifest icon.
if (install_surface_ == webapps::WebappInstallSource::SYSTEM_DEFAULT) {
DCHECK(opt_manifest);
DCHECK(!opt_manifest->icons.empty());
}
// If the manifest specified icons, don't use the page icons.
const bool skip_page_favicons = opt_manifest && !opt_manifest->icons.empty();
CheckForPlayStoreIntentOrGetIcons(std::move(opt_manifest),
std::move(web_app_info),
std::move(icon_urls), skip_page_favicons);
}
void WebAppInstallTask::CheckForPlayStoreIntentOrGetIcons(
blink::mojom::ManifestPtr opt_manifest,
std::unique_ptr<WebAppInstallInfo> web_app_info,
base::flat_set<GURL> icon_urls,
bool skip_page_favicons) {
bool is_create_shortcut = flow_ == WebAppInstallFlow::kCreateShortcut;
// Background installations are not a user-triggered installs, and thus
// cannot be sent to the store.
bool skip_store =
is_create_shortcut || background_installation_ || !opt_manifest;
if (!skip_store) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
absl::optional<PlayStoreIntent> intent =
GetPlayStoreIntentFromManifest(*opt_manifest);
if (intent) {
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (arc_service_manager) {
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->app(), IsInstallable);
if (instance) {
instance->IsInstallable(
intent->app_id,
base::BindOnce(&WebAppInstallTask::OnDidCheckForIntentToPlayStore,
GetWeakPtr(), std::move(web_app_info),
std::move(icon_urls), skip_page_favicons,
intent->intent));
return;
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (ShouldInteractWithArc()) {
absl::optional<PlayStoreIntent> intent =
GetPlayStoreIntentFromManifest(*opt_manifest);
mojo::Remote<crosapi::mojom::Arc>* opt_arc = GetArcRemoteWithMinVersion(
crosapi::mojom::Arc::MethodMinVersions::kIsInstallableMinVersion);
if (opt_arc && intent) {
mojo::Remote<crosapi::mojom::Arc>& arc = *opt_arc;
arc->IsInstallable(
intent->app_id,
base::BindOnce(
&WebAppInstallTask::OnDidCheckForIntentToPlayStoreLacros,
GetWeakPtr(), std::move(web_app_info), std::move(icon_urls),
skip_page_favicons, intent->intent));
return;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
}
OnDidCheckForIntentToPlayStore(std::move(web_app_info), std::move(icon_urls),
skip_page_favicons,
/*intent=*/"",
/*should_intent_to_store=*/false);
}
void WebAppInstallTask::OnDidCheckForIntentToPlayStore(
std::unique_ptr<WebAppInstallInfo> web_app_info,
base::flat_set<GURL> icon_urls,
bool skip_page_favicons,
const std::string& intent,
bool should_intent_to_store) {
if (ShouldStopInstall())
return;
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (should_intent_to_store && !intent.empty()) {
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (arc_service_manager) {
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
HandleUrl);
if (instance) {
instance->HandleUrl(intent, kPlayStorePackage);
CallInstallCallback(AppId(),
webapps::InstallResultCode::kIntentToPlayStore);
return;
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (ShouldInteractWithArc() && should_intent_to_store && !intent.empty()) {
mojo::Remote<crosapi::mojom::Arc>* opt_arc = GetArcRemoteWithMinVersion(
crosapi::mojom::Arc::MethodMinVersions::kHandleUrlMinVersion);
if (opt_arc) {
mojo::Remote<crosapi::mojom::Arc>& arc = *opt_arc;
arc->HandleUrl(intent, kPlayStorePackage);
CallInstallCallback(AppId(),
webapps::InstallResultCode::kIntentToPlayStore);
return;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
data_retriever_->GetIcons(
web_contents(), std::move(icon_urls), skip_page_favicons,
base::BindOnce(&WebAppInstallTask::OnIconsRetrievedShowDialog,
GetWeakPtr(), std::move(web_app_info)));
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
void WebAppInstallTask::OnDidCheckForIntentToPlayStoreLacros(
std::unique_ptr<WebAppInstallInfo> web_app_info,
base::flat_set<GURL> icon_urls,
bool skip_page_favicons,
const std::string& intent,
crosapi::mojom::IsInstallableResult result) {
OnDidCheckForIntentToPlayStore(
std::move(web_app_info), std::move(icon_urls), skip_page_favicons, intent,
result == crosapi::mojom::IsInstallableResult::kInstallable);
}
#endif
void WebAppInstallTask::OnIconsRetrievedShowDialog(
std::unique_ptr<WebAppInstallInfo> web_app_info,
IconsDownloadedResult result,
IconsMap icons_map,
DownloadedIconsHttpResults icons_http_results) {
if (ShouldStopInstall())
return;
DCHECK(web_app_info);
PopulateProductIcons(web_app_info.get(), &icons_map);
PopulateOtherIcons(web_app_info.get(), icons_map);
RecordDownloadedIconsResultAndHttpStatusCodes(result, icons_http_results);
log_entry_.LogDownloadedIconsErrors(*web_app_info, result, icons_map,
icons_http_results);
if (background_installation_) {
DCHECK(!dialog_callback_);
OnDialogCompleted(/*user_accepted=*/true, std::move(web_app_info));
} else {
DCHECK(dialog_callback_);
std::move(dialog_callback_)
.Run(web_contents(), std::move(web_app_info),
base::BindOnce(&WebAppInstallTask::OnDialogCompleted,
GetWeakPtr()));
}
}
void WebAppInstallTask::OnDialogCompleted(
bool user_accepted,
std::unique_ptr<WebAppInstallInfo> web_app_info) {
if (ShouldStopInstall())
return;
if (!user_accepted) {
CallInstallCallback(AppId(),
webapps::InstallResultCode::kUserInstallDeclined);
return;
}
if (only_retrieve_web_app_install_info_) {
if (web_app_info) {
web_app_install_info_ = std::move(*web_app_info);
web_app_info.reset();
}
CallInstallCallback(AppId(),
webapps::InstallResultCode::kSuccessNewInstall);
return;
}
WebAppInstallInfo web_app_info_copy = web_app_info->Clone();
// This metric is recorded regardless of the installation result.
RecordInstallEvent();
WebAppInstallFinalizer::FinalizeOptions finalize_options(install_surface_);
if (install_params_) {
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_copy.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;
} else {
finalize_options.locally_installed = true;
finalize_options.overwrite_existing_manifest_fields = true;
finalize_options.add_to_applications_menu = true;
finalize_options.add_to_desktop = true;
finalize_options.add_to_quick_launch_bar =
kAddAppsToQuickLaunchBarByDefault;
}
install_finalizer_->FinalizeInstall(
web_app_info_copy, finalize_options,
base::BindOnce(&WebAppInstallTask::OnInstallFinalizedMaybeReparentTab,
GetWeakPtr(), std::move(web_app_info)));
// Check that the finalizer hasn't called OnInstallFinalizedMaybeReparentTab
// synchronously:
DCHECK(install_callback_);
}
void WebAppInstallTask::OnInstallFinalized(const AppId& app_id,
webapps::InstallResultCode code,
OsHooksErrors os_hooks_errors) {
CallInstallCallback(app_id, code);
}
void WebAppInstallTask::OnInstallFinalizedMaybeReparentTab(
std::unique_ptr<WebAppInstallInfo> web_app_info,
const AppId& app_id,
webapps::InstallResultCode code,
OsHooksErrors os_hooks_errors) {
if (ShouldStopInstall())
return;
if (code != webapps::InstallResultCode::kSuccessNewInstall) {
CallInstallCallback(app_id, code);
return;
}
RecordWebAppInstallationTimestamp(profile_->GetPrefs(), app_id,
install_surface_);
if (install_params_ && !install_params_->locally_installed) {
DCHECK(background_installation_);
}
if (!install_params_ || install_params_->locally_installed) {
RecordAppBanner(web_contents(), web_app_info->start_url);
} else {
DCHECK(background_installation_);
}
if (!background_installation_ &&
install_surface_ != webapps::WebappInstallSource::SUB_APP) {
bool error = os_hooks_errors[OsHookType::kShortcuts];
const bool can_reparent_tab =
install_finalizer_->CanReparentTab(app_id, !error);
if (can_reparent_tab &&
(web_app_info->user_display_mode != mojom::UserDisplayMode::kBrowser)) {
install_finalizer_->ReparentTab(app_id, !error, web_contents());
}
}
CallInstallCallback(app_id, webapps::InstallResultCode::kSuccessNewInstall);
}
void WebAppInstallTask::RecordDownloadedIconsResultAndHttpStatusCodes(
IconsDownloadedResult result,
const DownloadedIconsHttpResults& icons_http_results) {
RecordDownloadedIconsHttpResultsCodeClass(
"WebApp.Icon.HttpStatusCodeClassOnCreate", result, icons_http_results);
UMA_HISTOGRAM_ENUMERATION("WebApp.Icon.DownloadedResultOnCreate", result);
RecordDownloadedIconHttpStatusCodes(
"WebApp.Icon.DownloadedHttpStatusCodeOnCreate", icons_http_results);
}
} // namespace web_app