blob: 9a8788a25a7d698dca9026e2a1e61bd034609dec [file] [log] [blame]
// Copyright 2018 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/web_applications/bookmark_apps/bookmark_app_install_manager.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/scoped_observer.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/extensions/bookmark_app_helper.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/installable/installable_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/install_finalizer.h"
#include "chrome/browser/web_applications/components/install_manager_observer.h"
#include "chrome/browser/web_applications/components/install_options.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/browser/web_applications/components/web_app_data_retriever.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/common/web_application_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/extension_system.h"
namespace extensions {
namespace {
// A single installation task. It is possible to have many concurrent
// installations per one |web_contents|.
// This class is a holder of WebAppDataRetriever or BookmarkAppHelper and limits
// their lifetime to match WebContents, InstallManager and Profile lifetimes.
class InstallTask : public content::WebContentsObserver,
public web_app::InstallManagerObserver {
public:
InstallTask(BookmarkAppInstallManager* install_manager,
web_app::InstallManager::OnceInstallCallback callback)
: WebContentsObserver(),
callback_(std::move(callback)) {
manager_observer_.Add(install_manager);
}
~InstallTask() override { DCHECK(!callback_); }
void SetWebContents(content::WebContents* web_contents) {
Observe(web_contents);
}
void SetBookmarkAppHelper(
std::unique_ptr<BookmarkAppHelper> bookmark_app_helper) {
DCHECK(!data_retriever_);
bookmark_app_helper_ = std::move(bookmark_app_helper);
}
void SetDataRetriever(
std::unique_ptr<web_app::WebAppDataRetriever> data_retriever) {
DCHECK(!bookmark_app_helper_);
data_retriever_ = std::move(data_retriever);
}
void ResetDataRetriever() { data_retriever_.reset(); }
// WebContentsObserver:
void WebContentsDestroyed() override {
DCHECK(callback_);
CallInstallCallback(web_app::AppId(),
web_app::InstallResultCode::kWebContentsDestroyed);
// BookmarkAppHelper should not outsurvive |web_contents|.
// This |reset| invalidates all weak references to BookmarkAppHelper and
// cancels BookmarkAppHelper-related tasks posted to message loops.
// This |ResetAndDeleteThisTask| call also destroys |this| InstallTask since
// it is owned by a callback bind state.
return ResetAndDeleteThisTask();
}
// InstallManagerObserver:
void InstallManagerReset() override {
DCHECK(callback_);
CallInstallCallback(web_app::AppId(),
web_app::InstallResultCode::kInstallManagerDestroyed);
// BookmarkAppHelper should not outsurvive profile and its keyed services.
// This |ResetAndDeleteThisTask| call also destroys |this| InstallTask since
// it is owned by a callback bind state.
return ResetAndDeleteThisTask();
}
void CallInstallCallback(const web_app::AppId& app_id,
web_app::InstallResultCode code) {
if (callback_)
std::move(callback_).Run(app_id, code);
Observe(nullptr);
manager_observer_.RemoveAll();
}
private:
void ResetAndDeleteThisTask() {
// One of these |reset|s deletes |this| InstallTask since it is owned by a
// callback bind state. We can not touch data members after it.
if (data_retriever_)
data_retriever_.reset();
else
bookmark_app_helper_.reset();
}
// The object invariant: InstallTask is owned either by data_retriever_ or
// bookmark_app_helper_. Just one of these ptrs is valid at any moment.
std::unique_ptr<web_app::WebAppDataRetriever> data_retriever_;
std::unique_ptr<BookmarkAppHelper> bookmark_app_helper_;
web_app::InstallManager::OnceInstallCallback callback_;
ScopedObserver<web_app::InstallManager, InstallTask> manager_observer_{this};
DISALLOW_COPY_AND_ASSIGN(InstallTask);
};
void DestroyInstallTask(std::unique_ptr<InstallTask> install_task) {
install_task.reset();
}
void OnBookmarkAppInstalled(std::unique_ptr<InstallTask> install_task,
const Extension* app,
const WebApplicationInfo& web_app_info) {
if (app) {
install_task->CallInstallCallback(app->id(),
web_app::InstallResultCode::kSuccess);
} else {
install_task->CallInstallCallback(
web_app::AppId(), web_app::InstallResultCode::kFailedUnknownReason);
}
// OnBookmarkAppInstalled is called synchronously by BookmarkAppHelper.
// Post async task to destroy InstallTask with BookmarkAppHelper later.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(DestroyInstallTask, std::move(install_task)));
}
WebappInstallSource ConvertOptionsToMetricsInstallSource(
const web_app::InstallOptions& options) {
WebappInstallSource metrics_install_source = WebappInstallSource::COUNT;
switch (options.install_source) {
case web_app::InstallSource::kInternal:
metrics_install_source = WebappInstallSource::INTERNAL_DEFAULT;
break;
case web_app::InstallSource::kExternalDefault:
metrics_install_source = WebappInstallSource::EXTERNAL_DEFAULT;
break;
case web_app::InstallSource::kExternalPolicy:
metrics_install_source = WebappInstallSource::EXTERNAL_POLICY;
break;
case web_app::InstallSource::kSystemInstalled:
metrics_install_source = WebappInstallSource::SYSTEM_DEFAULT;
break;
case web_app::InstallSource::kArc:
NOTREACHED();
break;
}
return metrics_install_source;
}
void SetBookmarkAppHelperOptions(const web_app::InstallOptions& options,
BookmarkAppHelper* helper) {
switch (options.launch_container) {
case web_app::LaunchContainer::kDefault:
break;
case web_app::LaunchContainer::kTab:
helper->set_forced_launch_type(LAUNCH_TYPE_REGULAR);
break;
case web_app::LaunchContainer::kWindow:
helper->set_forced_launch_type(LAUNCH_TYPE_WINDOW);
break;
}
switch (options.install_source) {
// TODO(nigeltao/ortuno): should these two cases lead to different
// Manifest::Location values: INTERNAL vs EXTERNAL_PREF_DOWNLOAD?
case web_app::InstallSource::kInternal:
case web_app::InstallSource::kExternalDefault:
helper->set_is_default_app();
break;
case web_app::InstallSource::kExternalPolicy:
helper->set_is_policy_installed_app();
break;
case web_app::InstallSource::kSystemInstalled:
helper->set_is_system_app();
break;
case web_app::InstallSource::kArc:
NOTREACHED();
break;
}
if (!options.add_to_applications_menu)
helper->set_skip_adding_to_applications_menu();
if (!options.add_to_desktop)
helper->set_skip_adding_to_desktop();
if (!options.add_to_quick_launch_bar)
helper->set_skip_adding_to_quick_launch_bar();
if (options.bypass_service_worker_check)
helper->set_bypass_service_worker_check();
if (options.require_manifest)
helper->set_require_manifest();
}
void OnGetWebApplicationInfo(const BookmarkAppInstallManager* install_manager,
std::unique_ptr<InstallTask> install_task,
const web_app::InstallOptions& install_options,
std::unique_ptr<WebApplicationInfo> web_app_info) {
if (!web_app_info) {
install_task->CallInstallCallback(
web_app::AppId(),
web_app::InstallResultCode::kGetWebApplicationInfoFailed);
// OnGetWebApplicationInfo is called synchronously by WebAppDataRetriever
// as a tail call. We can delete InstallTask with data_retriever_ safely.
install_task.reset();
return;
}
WebappInstallSource metrics_install_source =
ConvertOptionsToMetricsInstallSource(install_options);
Profile* profile = Profile::FromBrowserContext(
install_task->web_contents()->GetBrowserContext());
DCHECK(profile);
auto bookmark_app_helper = install_manager->bookmark_app_helper_factory().Run(
profile, std::move(web_app_info), install_task->web_contents(),
metrics_install_source);
BookmarkAppHelper* helper_ptr = bookmark_app_helper.get();
SetBookmarkAppHelperOptions(install_options, bookmark_app_helper.get());
install_task->ResetDataRetriever();
install_task->SetBookmarkAppHelper(std::move(bookmark_app_helper));
helper_ptr->Create(base::BindRepeating(
OnBookmarkAppInstalled, base::Passed(std::move(install_task))));
}
} // namespace
BookmarkAppInstallManager::BookmarkAppInstallManager(
Profile* profile,
web_app::InstallFinalizer* finalizer)
: InstallManager(profile), finalizer_(finalizer) {
bookmark_app_helper_factory_ = base::BindRepeating(
[](Profile* profile, std::unique_ptr<WebApplicationInfo> web_app_info,
content::WebContents* web_contents,
WebappInstallSource install_source) {
return std::make_unique<BookmarkAppHelper>(
profile, std::move(web_app_info), web_contents, install_source);
});
data_retriever_factory_ = base::BindRepeating(
[]() { return std::make_unique<web_app::WebAppDataRetriever>(); });
}
BookmarkAppInstallManager::~BookmarkAppInstallManager() = default;
bool BookmarkAppInstallManager::CanInstallWebApp(
content::WebContents* web_contents) {
return extensions::TabHelper::FromWebContents(web_contents)
->CanCreateBookmarkApp();
}
void BookmarkAppInstallManager::InstallWebAppFromManifestWithFallback(
content::WebContents* web_contents,
bool force_shortcut_app,
WebappInstallSource install_source,
WebAppInstallDialogCallback dialog_callback,
OnceInstallCallback callback) {
// |dialog_callback| is ignored here: BookmarkAppHelper directly uses UI's
// chrome::ShowPWAInstallDialog.
// |install_source| is also ignored here: extensions::TabHelper specifies it.
extensions::TabHelper::FromWebContents(web_contents)
->CreateHostedAppFromWebContents(
force_shortcut_app,
base::BindOnce(
[](OnceInstallCallback callback, const ExtensionId& app_id,
bool success) {
std::move(callback).Run(
app_id,
success ? web_app::InstallResultCode::kSuccess
: web_app::InstallResultCode::kFailedUnknownReason);
},
std::move(callback)));
}
void BookmarkAppInstallManager::InstallWebAppFromManifest(
content::WebContents* web_contents,
WebappInstallSource install_source,
WebAppInstallDialogCallback dialog_callback,
OnceInstallCallback callback) {
// |dialog_callback| is ignored here:
// BookmarkAppHelper directly uses UI's chrome::ShowPWAInstallDialog.
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
auto bookmark_app_helper = std::make_unique<BookmarkAppHelper>(
profile, std::make_unique<WebApplicationInfo>(), web_contents,
install_source);
BookmarkAppHelper* helper = bookmark_app_helper.get();
auto install_task = std::make_unique<InstallTask>(this, std::move(callback));
install_task->SetBookmarkAppHelper(std::move(bookmark_app_helper));
install_task->SetWebContents(web_contents);
// InstallTask is owned by the bind state and will be disposed in
// DestroyInstallTask.
helper->Create(base::BindRepeating(OnBookmarkAppInstalled,
base::Passed(std::move(install_task))));
}
void BookmarkAppInstallManager::InstallWebAppFromInfo(
std::unique_ptr<WebApplicationInfo> web_application_info,
bool no_network_install,
WebappInstallSource install_source,
OnceInstallCallback callback) {
auto bookmark_app_helper = std::make_unique<BookmarkAppHelper>(
profile(), std::move(web_application_info), /*web_contents=*/nullptr,
install_source);
if (no_network_install) {
// We should only install windowed apps via this method.
bookmark_app_helper->set_forced_launch_type(extensions::LAUNCH_TYPE_WINDOW);
bookmark_app_helper->set_is_no_network_install();
}
BookmarkAppHelper* helper = bookmark_app_helper.get();
auto install_task = std::make_unique<InstallTask>(this, std::move(callback));
install_task->SetBookmarkAppHelper(std::move(bookmark_app_helper));
// InstallTask is owned by the bind state and will be disposed in
// DestroyInstallTask.
helper->Create(base::BindRepeating(OnBookmarkAppInstalled,
base::Passed(std::move(install_task))));
}
void BookmarkAppInstallManager::InstallWebAppWithOptions(
content::WebContents* web_contents,
const web_app::InstallOptions& install_options,
OnceInstallCallback callback) {
auto data_retriever = data_retriever_factory_.Run();
web_app::WebAppDataRetriever* data_retriever_ptr = data_retriever.get();
auto install_task = std::make_unique<InstallTask>(this, std::move(callback));
install_task->SetDataRetriever(std::move(data_retriever));
install_task->SetWebContents(web_contents);
// Unretained |this| is used here because |install_task| is
// InstallManagerObserver which guarantees that it'll never run the callback
// if |this| is destroyed.
data_retriever_ptr->GetWebApplicationInfo(
web_contents,
base::BindOnce(OnGetWebApplicationInfo, base::Unretained(this),
std::move(install_task), install_options));
}
void BookmarkAppInstallManager::InstallOrUpdateWebAppFromSync(
const web_app::AppId& app_id,
std::unique_ptr<WebApplicationInfo> web_application_info,
OnceInstallCallback callback) {
// |callback| is ignored here: the legacy system doesn't report completion.
ExtensionService* extension_service =
ExtensionSystem::Get(profile())->extension_service();
DCHECK(extension_service);
if (finalizer_->CanSkipAppUpdateForSync(app_id, *web_application_info))
return;
#if defined(OS_CHROMEOS)
// On Chrome OS, sync always locally installs an app.
bool is_locally_installed = true;
#else
bool is_locally_installed =
extension_service->GetInstalledExtension(app_id) != nullptr;
#endif
CreateOrUpdateBookmarkApp(extension_service, web_application_info.get(),
is_locally_installed);
}
void BookmarkAppInstallManager::InstallWebAppForTesting(
std::unique_ptr<WebApplicationInfo> web_application_info,
OnceInstallCallback callback) {
// |callback| is ignored here: the legacy system doesn't report completion.
ExtensionService* extension_service =
ExtensionSystem::Get(profile())->extension_service();
DCHECK(extension_service);
CreateOrUpdateBookmarkApp(extension_service, web_application_info.get(),
true /*is_locally_installed*/);
}
void BookmarkAppInstallManager::SetBookmarkAppHelperFactoryForTesting(
BookmarkAppHelperFactory bookmark_app_helper_factory) {
bookmark_app_helper_factory_ = std::move(bookmark_app_helper_factory);
}
void BookmarkAppInstallManager::SetDataRetrieverFactoryForTesting(
DataRetrieverFactory data_retriever_factory) {
data_retriever_factory_ = std::move(data_retriever_factory);
}
} // namespace extensions