blob: 7c6130e5d571056da34e9001387b8991c63bd1f4 [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/web_app_install_manager.h"
#include <iterator>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/commands/install_from_sync_command.h"
#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
#include "chrome/browser/web_applications/install_bounce_metric.h"
#include "chrome/browser/web_applications/user_display_mode.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_data_retriever.h"
#include "chrome/browser/web_applications/web_app_install_finalizer.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_internals_utils.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "content/public/browser/web_contents.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace web_app {
namespace {
bool TaskExpectsAppId(const WebAppInstallTask* task, const AppId& app_id) {
return task && task->app_id_to_expect().has_value() &&
task->app_id_to_expect().value() == app_id;
}
constexpr char kWebAppInstallManagerName[] = "WebAppInstallManager";
} // namespace
WebAppInstallManager::WebAppInstallManager(Profile* profile)
: profile_(profile), url_loader_(std::make_unique<WebAppUrlLoader>()) {
data_retriever_factory_ = base::BindRepeating(
[]() { return std::make_unique<WebAppDataRetriever>(); });
if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) {
error_log_ = std::make_unique<ErrorLog>();
ReadErrorLog(GetWebAppsRootDirectory(profile_), kWebAppInstallManagerName,
base::BindOnce(&WebAppInstallManager::OnReadErrorLog,
weak_ptr_factory_.GetWeakPtr()));
} else {
ClearErrorLog(GetWebAppsRootDirectory(profile_), kWebAppInstallManagerName,
base::DoNothing());
}
}
WebAppInstallManager::~WebAppInstallManager() {
NotifyWebAppInstallManagerDestroyed();
}
void WebAppInstallManager::Start() {
DCHECK(!started_);
started_ = true;
}
void WebAppInstallManager::Shutdown() {
// Set the `started_` flag to false first so when we delete tasks below any
// task that re-enters or uses this manager instance will see we're (going)
// offline.
started_ = false;
tasks_.clear();
{
TaskQueue empty;
task_queue_.swap(empty);
}
url_loader_.reset();
web_contents_.reset();
}
bool WebAppInstallManager::IsInstallingForWebContents(
const content::WebContents* web_contents) const {
return base::ranges::any_of(
tasks_, [web_contents](const std::unique_ptr<WebAppInstallTask>& task) {
return task->GetInstallingWebContents() == web_contents;
});
}
std::size_t WebAppInstallManager::GetInstallTaskCountForTesting() const {
return tasks_.size();
}
void WebAppInstallManager::SetSubsystems(
WebAppRegistrar* registrar,
OsIntegrationManager* os_integration_manager,
WebAppCommandManager* command_manager,
WebAppInstallFinalizer* finalizer,
WebAppIconManager* icon_manager,
WebAppSyncBridge* sync_bridge,
WebAppTranslationManager* translation_manager) {
registrar_ = registrar;
os_integration_manager_ = os_integration_manager;
command_manager_ = command_manager;
finalizer_ = finalizer;
icon_manager_ = icon_manager;
sync_bridge_ = sync_bridge;
translation_manager_ = translation_manager;
}
void WebAppInstallManager::InstallSubApp(
const AppId& parent_app_id,
const GURL& install_url,
const AppId& expected_app_id,
WebAppInstallDialogCallback dialog_callback,
OnceInstallCallback install_callback) {
if (!started_)
return;
auto task = std::make_unique<WebAppInstallTask>(
profile_, finalizer_, data_retriever_factory_.Run(), registrar_,
webapps::WebappInstallSource::SUB_APP);
WebAppInstallParams params;
params.parent_app_id = parent_app_id;
params.require_manifest = true;
params.add_to_quick_launch_bar = false;
params.user_display_mode = UserDisplayMode::kStandalone;
params.fallback_start_url = install_url;
// Don't want to allow devs to force manifest updates with the API.
params.force_reinstall = false;
params.install_url = install_url;
task->SetInstallParams(params);
WebAppInstallTask* task_ptr = task.get();
tasks_.insert(std::move(task));
task_ptr->LoadAndInstallSubAppFromURL(
install_url, expected_app_id, EnsureWebContentsCreated(),
url_loader_.get(), std::move(dialog_callback),
base::BindOnce(&WebAppInstallManager::OnInstallTaskCompleted,
GetWeakPtr(), task_ptr, std::move(install_callback)));
}
base::WeakPtr<WebAppInstallManager> WebAppInstallManager::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
std::set<AppId> WebAppInstallManager::GetEnqueuedInstallAppIdsForTesting() {
std::set<AppId> app_ids;
if (current_queued_task_ &&
current_queued_task_->app_id_to_expect().has_value()) {
app_ids.insert(current_queued_task_->app_id_to_expect().value());
}
for (const std::unique_ptr<WebAppInstallTask>& task : tasks_) {
if (task && task->app_id_to_expect().has_value())
app_ids.insert(task->app_id_to_expect().value());
}
return app_ids;
}
bool WebAppInstallManager::IsAppIdAlreadyEnqueued(const AppId& app_id) const {
if (TaskExpectsAppId(current_queued_task_, app_id))
return true;
for (const std::unique_ptr<WebAppInstallTask>& task : tasks_) {
if (TaskExpectsAppId(task.get(), app_id))
return true;
}
return false;
}
void WebAppInstallManager::InstallWebAppsAfterSync(
std::vector<WebApp*> web_apps,
RepeatingInstallCallback callback) {
if (!started_)
return;
if (install_web_apps_after_sync_delegate_) {
install_web_apps_after_sync_delegate_.Run(std::move(web_apps), callback);
} else {
for (WebApp* web_app : web_apps) {
DCHECK(web_app->is_from_sync_and_pending_installation());
InstallFromSyncCommand::Params params = InstallFromSyncCommand::Params(
web_app->app_id(), web_app->manifest_id(), web_app->start_url(),
web_app->sync_fallback_data().name,
web_app->sync_fallback_data().scope,
web_app->sync_fallback_data().theme_color,
web_app->user_display_mode(),
web_app->sync_fallback_data().icon_infos);
command_manager_->ScheduleCommand(
std::make_unique<InstallFromSyncCommand>(
url_loader_.get(), profile_, finalizer_.get(), registrar_.get(),
data_retriever_factory_.Run(), params, callback));
}
}
}
void WebAppInstallManager::UninstallFromSync(
const std::vector<AppId>& web_apps,
RepeatingUninstallCallback callback) {
if (!started_)
return;
if (uninstall_from_sync_before_registry_update_delegate_) {
uninstall_from_sync_before_registry_update_delegate_.Run(web_apps,
callback);
} else {
if (uninstall_callback_for_testing_)
callback = uninstall_callback_for_testing_;
for (auto& app_id : web_apps) {
// Sync uninstalls do not require an install source to be passed.
finalizer_->ScheduleUninstallCommand(
app_id, /*external_install_source=*/absl::nullopt,
webapps::WebappUninstallSource::kSync,
base::BindOnce(callback, app_id));
}
}
}
void WebAppInstallManager::RetryIncompleteUninstalls(
const base::flat_set<AppId>& apps_to_uninstall) {
if (!started_)
return;
if (retry_incomplete_uninstalls_delegate_)
retry_incomplete_uninstalls_delegate_.Run(apps_to_uninstall);
else
finalizer_->RetryIncompleteUninstalls(apps_to_uninstall);
}
void WebAppInstallManager::SetDataRetrieverFactoryForTesting(
DataRetrieverFactory data_retriever_factory) {
data_retriever_factory_ = std::move(data_retriever_factory);
}
void WebAppInstallManager::SetUrlLoaderForTesting(
std::unique_ptr<WebAppUrlLoader> url_loader) {
url_loader_ = std::move(url_loader);
}
void WebAppInstallManager::EnqueueTask(std::unique_ptr<WebAppInstallTask> task,
base::OnceClosure start_task) {
DCHECK(web_contents_);
PendingTask pending_task;
pending_task.task = task.get();
pending_task.start = std::move(start_task);
task_queue_.push(std::move(pending_task));
tasks_.insert(std::move(task));
MaybeStartQueuedTask();
}
void WebAppInstallManager::MaybeStartQueuedTask() {
if (!started_)
return;
DCHECK(web_contents_);
if (current_queued_task_)
return;
DCHECK(!task_queue_.empty());
PendingTask pending_task = std::move(task_queue_.front());
task_queue_.pop();
current_queued_task_ = pending_task.task;
url_loader_->PrepareForLoad(
web_contents_.get(),
base::BindOnce(&WebAppInstallManager::OnWebContentsReadyRunTask,
GetWeakPtr(), std::move(pending_task)));
}
void WebAppInstallManager::TakeTaskErrorLog(WebAppInstallTask* task) {
if (error_log_) {
base::Value task_error_dict = task->TakeErrorDict();
if (!task_error_dict.DictEmpty())
LogErrorObject(std::move(task_error_dict));
}
}
void WebAppInstallManager::TakeCommandErrorLog(
base::PassKey<WebAppCommandManager>,
base::Value log) {
if (error_log_)
LogErrorObject(std::move(log));
}
void WebAppInstallManager::SetUninstallCallbackForTesting(
RepeatingUninstallCallback uninstall_callback_for_testing) {
uninstall_callback_for_testing_ = uninstall_callback_for_testing;
}
void WebAppInstallManager::SetInstallWebAppsAfterSyncDelegateForTesting(
InstallWebAppsAfterSyncDelegate delegate) {
install_web_apps_after_sync_delegate_ = std::move(delegate);
}
void WebAppInstallManager::SetUninstallFromSyncDelegateForTesting(
UninstallFromSyncDelegate delegate) {
uninstall_from_sync_before_registry_update_delegate_ = std::move(delegate);
}
void WebAppInstallManager::SetRetryIncompleteUninstallsDelegateForTesting(
RetryIncompleteUninstallsDelegate delegate) {
retry_incomplete_uninstalls_delegate_ = std::move(delegate);
}
void WebAppInstallManager::DeleteTask(WebAppInstallTask* task) {
TakeTaskErrorLog(task);
// If this happens after/during the call to Shutdown(), then ignore deletion
// as `tasks_` is emptied already.
if (started_) {
DCHECK(tasks_.contains(task));
tasks_.erase(task);
}
}
void WebAppInstallManager::OnInstallTaskCompleted(
WebAppInstallTask* task,
OnceInstallCallback callback,
const AppId& app_id,
webapps::InstallResultCode code) {
DeleteTask(task);
std::move(callback).Run(app_id, code);
}
void WebAppInstallManager::OnQueuedTaskCompleted(
WebAppInstallTask* task,
OnceInstallCallback callback,
const AppId& app_id,
webapps::InstallResultCode code) {
DCHECK(current_queued_task_);
DCHECK_EQ(current_queued_task_, task);
current_queued_task_ = nullptr;
OnInstallTaskCompleted(task, std::move(callback), app_id, code);
// |task| is now destroyed.
task = nullptr;
if (task_queue_.empty() && !current_queued_task_)
web_contents_.reset();
else
MaybeStartQueuedTask();
}
content::WebContents* WebAppInstallManager::EnsureWebContentsCreated() {
if (!web_contents_)
web_contents_ = WebAppInstallTask::CreateWebContents(profile_);
return web_contents_.get();
}
void WebAppInstallManager::OnWebContentsReadyRunTask(
PendingTask pending_task,
WebAppUrlLoader::Result result) {
if (!web_contents_) {
DCHECK(!started_);
return;
}
// about:blank must always be loaded.
DCHECK_EQ(WebAppUrlLoader::Result::kUrlLoaded, result);
if (result != WebAppUrlLoader::Result::kUrlLoaded)
LogUrlLoaderError("OnWebContentsReady", pending_task, result);
std::move(pending_task.start).Run();
}
void WebAppInstallManager::LogUrlLoaderError(const char* stage,
const PendingTask& pending_task,
WebAppUrlLoader::Result result) {
if (!error_log_)
return;
base::Value url_loader_error(base::Value::Type::DICTIONARY);
url_loader_error.SetStringKey("WebAppUrlLoader::Result",
ConvertUrlLoaderResultToString(result));
if (pending_task.task->app_id_to_expect().has_value()) {
url_loader_error.SetStringKey(
"task.app_id_to_expect", pending_task.task->app_id_to_expect().value());
}
LogErrorObjectAtStage(stage, std::move(url_loader_error));
}
void WebAppInstallManager::MaybeWriteErrorLog() {
DCHECK(error_log_);
if (error_log_writing_in_progress_ || !error_log_updated_)
return;
WriteErrorLog(GetWebAppsRootDirectory(profile_), kWebAppInstallManagerName,
base::Value(error_log_->Clone()),
base::BindOnce(&WebAppInstallManager::OnWriteErrorLog,
weak_ptr_factory_.GetWeakPtr()));
error_log_writing_in_progress_ = true;
error_log_updated_ = false;
}
void WebAppInstallManager::OnWriteErrorLog(Result result) {
error_log_writing_in_progress_ = false;
MaybeWriteErrorLog();
}
void WebAppInstallManager::OnReadErrorLog(Result result,
base::Value error_log) {
DCHECK(error_log_);
if (result != Result::kOk || !error_log.is_list())
return;
ErrorLog early_error_log = std::move(*error_log_);
*error_log_ = std::move(error_log.GetList());
// Appends the `early_error_log` at the end.
error_log_->reserve(error_log_->size() + early_error_log.size());
for (auto& error : early_error_log) {
error_log_->Append(std::move(error));
}
}
void WebAppInstallManager::LogErrorObject(base::Value object) {
if (!error_log_)
return;
error_log_->Append(std::move(object));
error_log_updated_ = true;
MaybeWriteErrorLog();
}
void WebAppInstallManager::LogErrorObjectAtStage(const char* stage,
base::Value object) {
if (!error_log_)
return;
object.SetStringKey("!stage", stage);
LogErrorObject(std::move(object));
}
void WebAppInstallManager::AddObserver(WebAppInstallManagerObserver* observer) {
observers_.AddObserver(observer);
}
void WebAppInstallManager::RemoveObserver(
WebAppInstallManagerObserver* observer) {
observers_.RemoveObserver(observer);
}
void WebAppInstallManager::NotifyWebAppInstalled(const AppId& app_id) {
for (WebAppInstallManagerObserver& observer : observers_) {
observer.OnWebAppInstalled(app_id);
}
// TODO(alancutter): Call RecordWebAppInstallation here when we get access to
// the webapps::WebappInstallSource in this event.
}
void WebAppInstallManager::NotifyWebAppUninstalled(const AppId& app_id) {
for (WebAppInstallManagerObserver& observer : observers_) {
observer.OnWebAppUninstalled(app_id);
}
}
void WebAppInstallManager::NotifyWebAppManifestUpdated(
const AppId& app_id,
base::StringPiece old_name) {
for (WebAppInstallManagerObserver& observer : observers_) {
observer.OnWebAppManifestUpdated(app_id, old_name);
}
}
void WebAppInstallManager::NotifyWebAppWillBeUninstalled(const AppId& app_id) {
for (WebAppInstallManagerObserver& observer : observers_) {
observer.OnWebAppWillBeUninstalled(app_id);
}
RecordWebAppUninstallation(profile_->GetPrefs(), app_id);
}
void WebAppInstallManager::NotifyWebAppInstallManagerDestroyed() {
for (WebAppInstallManagerObserver& observer : observers_) {
observer.OnWebAppInstallManagerDestroyed();
}
}
void WebAppInstallManager::NotifyWebAppInstalledWithOsHooks(
const AppId& app_id) {
for (WebAppInstallManagerObserver& obs : observers_) {
obs.OnWebAppInstalledWithOsHooks(app_id);
}
}
WebAppInstallManager::PendingTask::PendingTask() = default;
WebAppInstallManager::PendingTask::PendingTask(PendingTask&&) noexcept =
default;
WebAppInstallManager::PendingTask::~PendingTask() = default;
} // namespace web_app