| // 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 <utility> |
| |
| #include "chrome/browser/web_applications/web_app_install_manager.h" |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/components/app_registrar.h" |
| #include "chrome/browser/web_applications/components/install_finalizer.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_utils.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_install_task.h" |
| #include "chrome/common/web_application_info.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace web_app { |
| |
| WebAppInstallManager::WebAppInstallManager(Profile* profile) |
| : InstallManager(profile), |
| url_loader_(std::make_unique<WebAppUrlLoader>()) { |
| data_retriever_factory_ = base::BindRepeating( |
| []() { return std::make_unique<WebAppDataRetriever>(); }); |
| } |
| |
| WebAppInstallManager::~WebAppInstallManager() = default; |
| |
| void WebAppInstallManager::LoadWebAppAndCheckInstallability( |
| const GURL& web_app_url, |
| WebappInstallSource install_source, |
| WebAppInstallabilityCheckCallback callback) { |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| |
| task->LoadWebAppAndCheckInstallability( |
| web_app_url, install_source, url_loader_.get(), |
| base::BindOnce( |
| &WebAppInstallManager::OnLoadWebAppAndCheckInstallabilityCompleted, |
| base::Unretained(this), task.get(), std::move(callback))); |
| |
| tasks_.insert(std::move(task)); |
| } |
| |
| void WebAppInstallManager::InstallWebAppFromManifest( |
| content::WebContents* contents, |
| WebappInstallSource install_source, |
| WebAppInstallDialogCallback dialog_callback, |
| OnceInstallCallback callback) { |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| task->InstallWebAppFromManifest( |
| contents, install_source, std::move(dialog_callback), |
| base::BindOnce(&WebAppInstallManager::OnInstallTaskCompleted, |
| base::Unretained(this), task.get(), std::move(callback))); |
| |
| tasks_.insert(std::move(task)); |
| } |
| |
| void WebAppInstallManager::InstallWebAppFromManifestWithFallback( |
| content::WebContents* contents, |
| bool force_shortcut_app, |
| WebappInstallSource install_source, |
| WebAppInstallDialogCallback dialog_callback, |
| OnceInstallCallback callback) { |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| task->InstallWebAppFromManifestWithFallback( |
| contents, force_shortcut_app, install_source, std::move(dialog_callback), |
| base::BindOnce(&WebAppInstallManager::OnInstallTaskCompleted, |
| base::Unretained(this), task.get(), std::move(callback))); |
| |
| tasks_.insert(std::move(task)); |
| } |
| |
| void WebAppInstallManager::InstallWebAppFromInfo( |
| std::unique_ptr<WebApplicationInfo> web_application_info, |
| ForInstallableSite for_installable_site, |
| WebappInstallSource install_source, |
| OnceInstallCallback callback) { |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| task->InstallWebAppFromInfo( |
| std::move(web_application_info), for_installable_site, install_source, |
| base::BindOnce(&WebAppInstallManager::OnInstallTaskCompleted, |
| base::Unretained(this), task.get(), std::move(callback))); |
| |
| tasks_.insert(std::move(task)); |
| } |
| |
| void WebAppInstallManager::InstallWebAppWithParams( |
| content::WebContents* web_contents, |
| const InstallParams& install_params, |
| WebappInstallSource install_source, |
| OnceInstallCallback callback) { |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| task->InstallWebAppWithParams( |
| web_contents, install_params, install_source, |
| base::BindOnce(&WebAppInstallManager::OnInstallTaskCompleted, |
| base::Unretained(this), task.get(), std::move(callback))); |
| |
| tasks_.insert(std::move(task)); |
| } |
| |
| void WebAppInstallManager::InstallBookmarkAppFromSync( |
| const AppId& bookmark_app_id, |
| std::unique_ptr<WebApplicationInfo> web_application_info, |
| OnceInstallCallback callback) { |
| // Skip sync update if app exists. |
| // All manifest fields will be set locally via update (see crbug.com/926083) |
| // so we must not sync them in order to avoid a device-to-device sync war. |
| if (registrar()->IsInstalled(bookmark_app_id)) { |
| std::move(callback).Run(bookmark_app_id, |
| InstallResultCode::kSuccessAlreadyInstalled); |
| return; |
| } |
| |
| bool is_locally_installed = false; |
| #if defined(OS_CHROMEOS) |
| // On Chrome OS, sync always locally installs an app. |
| is_locally_installed = true; |
| #endif |
| |
| // If bookmark_app_id is not installed enqueue full background installation |
| // flow. This install may produce a web app or an extension-based bookmark |
| // app, depending on the BMO flag. |
| GURL launch_url = web_application_info->app_url; |
| |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| |
| task->ExpectAppId(bookmark_app_id); |
| |
| InstallParams params; |
| params.user_display_mode = web_application_info->open_as_window |
| ? DisplayMode::kStandalone |
| : DisplayMode::kBrowser; |
| params.fallback_start_url = launch_url; |
| // If app is not locally installed then no OS integration like OS shortcuts. |
| params.locally_installed = is_locally_installed; |
| params.add_to_applications_menu = is_locally_installed; |
| params.add_to_desktop = is_locally_installed; |
| params.add_to_quick_launch_bar = is_locally_installed; |
| task->SetInstallParams(params); |
| |
| OnceInstallCallback task_completed_callback = base::BindOnce( |
| &WebAppInstallManager::OnBookmarkAppInstalledAfterSync, |
| base::Unretained(this), bookmark_app_id, std::move(web_application_info), |
| is_locally_installed, std::move(callback)); |
| |
| base::OnceClosure start_task = base::BindOnce( |
| &WebAppInstallTask::LoadAndInstallWebAppFromManifestWithFallback, |
| base::Unretained(task.get()), launch_url, EnsureWebContentsCreated(), |
| base::Unretained(url_loader_.get()), WebappInstallSource::SYNC, |
| base::BindOnce(&WebAppInstallManager::OnQueuedTaskCompleted, |
| base::Unretained(this), task.get(), |
| std::move(task_completed_callback))); |
| |
| EnqueueTask(std::move(task), std::move(start_task)); |
| } |
| |
| void WebAppInstallManager::UpdateWebAppFromInfo( |
| const AppId& app_id, |
| std::unique_ptr<WebApplicationInfo> web_application_info, |
| OnceInstallCallback callback) { |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| |
| base::OnceClosure start_task = base::BindOnce( |
| &WebAppInstallTask::UpdateWebAppFromInfo, base::Unretained(task.get()), |
| EnsureWebContentsCreated(), app_id, std::move(web_application_info), |
| base::BindOnce(&WebAppInstallManager::OnQueuedTaskCompleted, |
| base::Unretained(this), task.get(), std::move(callback))); |
| |
| EnqueueTask(std::move(task), std::move(start_task)); |
| } |
| |
| void WebAppInstallManager::Shutdown() { |
| tasks_.clear(); |
| { |
| TaskQueue empty; |
| task_queue_.swap(empty); |
| } |
| web_contents_.reset(); |
| } |
| |
| void WebAppInstallManager::InstallWebAppsAfterSync( |
| std::vector<WebApp*> web_apps, |
| RepeatingInstallCallback callback) { |
| for (WebApp* web_app : web_apps) { |
| DCHECK(web_app->is_in_sync_install()); |
| |
| auto task = std::make_unique<WebAppInstallTask>( |
| profile(), registrar(), shortcut_manager(), file_handler_manager(), |
| finalizer(), data_retriever_factory_.Run()); |
| |
| task->ExpectAppId(web_app->app_id()); |
| |
| OnceInstallCallback sync_install_callback = |
| base::BindOnce(&WebAppInstallManager::OnWebAppInstalledAfterSync, |
| base::Unretained(this), web_app->app_id(), callback); |
| |
| base::OnceClosure start_task = base::BindOnce( |
| &WebAppInstallTask::LoadAndInstallWebAppFromManifestWithFallback, |
| base::Unretained(task.get()), web_app->launch_url(), |
| EnsureWebContentsCreated(), base::Unretained(url_loader_.get()), |
| WebappInstallSource::SYNC, |
| base::BindOnce(&WebAppInstallManager::OnQueuedTaskCompleted, |
| base::Unretained(this), task.get(), |
| std::move(sync_install_callback))); |
| |
| EnqueueTask(std::move(task), std::move(start_task)); |
| } |
| } |
| |
| void WebAppInstallManager::UninstallWebAppsAfterSync( |
| std::vector<std::unique_ptr<WebApp>> web_apps, |
| RepeatingUninstallCallback callback) { |
| for (std::unique_ptr<WebApp>& web_app : web_apps) { |
| const AppId& app_id = web_app->app_id(); |
| |
| finalizer()->FinalizeUninstallAfterSync( |
| app_id, |
| base::BindOnce(&WebAppInstallManager::OnWebAppUninstalledAfterSync, |
| weak_ptr_factory_.GetWeakPtr(), std::move(web_app), |
| callback)); |
| } |
| } |
| |
| void WebAppInstallManager::SetUrlLoaderForTesting( |
| std::unique_ptr<WebAppUrlLoader> url_loader) { |
| url_loader_ = std::move(url_loader); |
| } |
| |
| void WebAppInstallManager::OnBookmarkAppInstalledAfterSync( |
| const AppId& bookmark_app_id, |
| std::unique_ptr<WebApplicationInfo> web_application_info, |
| bool is_locally_installed, |
| OnceInstallCallback callback, |
| const AppId& web_app_id, |
| InstallResultCode code) { |
| // TODO(loyso): Record |code| for this specific case in |
| // Webapp.BookmarkAppInstalledAfterSyncResult UMA. |
| if (IsSuccess(code)) { |
| DCHECK_EQ(bookmark_app_id, web_app_id); |
| std::move(callback).Run(web_app_id, code); |
| } else { |
| // Install failed. Do the fallback install from info. |
| InstallFinalizer::FinalizeOptions options; |
| options.install_source = WebappInstallSource::SYNC; |
| options.locally_installed = is_locally_installed; |
| |
| FilterAndResizeIconsGenerateMissing(web_application_info.get(), |
| /*icons_map=*/nullptr); |
| |
| finalizer()->FinalizeInstall(*web_application_info, options, |
| std::move(callback)); |
| } |
| } |
| |
| void WebAppInstallManager::EnqueueTask(std::unique_ptr<WebAppInstallTask> task, |
| base::OnceClosure start_task) { |
| DCHECK(web_contents_); |
| |
| tasks_.insert(std::move(task)); |
| task_queue_.push(std::move(start_task)); |
| |
| if (web_contents_ready_) |
| MaybeStartQueuedTask(); |
| } |
| |
| void WebAppInstallManager::MaybeStartQueuedTask() { |
| DCHECK(web_contents_ready_); |
| DCHECK(!task_queue_.empty()); |
| |
| if (is_running_queued_task_) |
| return; |
| |
| is_running_queued_task_ = true; |
| |
| base::OnceClosure task_closure = std::move(task_queue_.front()); |
| task_queue_.pop(); |
| |
| std::move(task_closure).Run(); |
| } |
| |
| void WebAppInstallManager::SetDataRetrieverFactoryForTesting( |
| DataRetrieverFactory data_retriever_factory) { |
| data_retriever_factory_ = std::move(data_retriever_factory); |
| } |
| |
| void WebAppInstallManager::DeleteTask(WebAppInstallTask* task) { |
| DCHECK(tasks_.contains(task)); |
| tasks_.erase(task); |
| } |
| |
| void WebAppInstallManager::OnInstallTaskCompleted(WebAppInstallTask* task, |
| OnceInstallCallback callback, |
| const AppId& app_id, |
| InstallResultCode code) { |
| DeleteTask(task); |
| |
| std::move(callback).Run(app_id, code); |
| } |
| |
| void WebAppInstallManager::OnQueuedTaskCompleted(WebAppInstallTask* task, |
| OnceInstallCallback callback, |
| const AppId& app_id, |
| InstallResultCode code) { |
| DCHECK(is_running_queued_task_); |
| is_running_queued_task_ = false; |
| |
| OnInstallTaskCompleted(task, std::move(callback), app_id, code); |
| task = nullptr; |
| |
| if (task_queue_.empty()) { |
| web_contents_.reset(); |
| web_contents_ready_ = false; |
| } else { |
| MaybeStartQueuedTask(); |
| } |
| } |
| |
| void WebAppInstallManager::OnLoadWebAppAndCheckInstallabilityCompleted( |
| WebAppInstallTask* task, |
| WebAppInstallabilityCheckCallback callback, |
| std::unique_ptr<content::WebContents> web_contents, |
| const AppId& app_id, |
| InstallResultCode code) { |
| DeleteTask(task); |
| |
| InstallableCheckResult result; |
| base::Optional<AppId> opt_app_id; |
| if (IsSuccess(code)) { |
| if (!app_id.empty() && registrar()->IsInstalled(app_id)) { |
| result = InstallableCheckResult::kAlreadyInstalled; |
| opt_app_id = app_id; |
| } else { |
| result = InstallableCheckResult::kInstallable; |
| } |
| } else { |
| result = InstallableCheckResult::kNotInstallable; |
| } |
| |
| std::move(callback).Run(std::move(web_contents), result, opt_app_id); |
| } |
| |
| void WebAppInstallManager::OnWebAppInstalledAfterSync( |
| const AppId& app_in_sync_install_id, |
| OnceInstallCallback callback, |
| const AppId& installed_app_id, |
| InstallResultCode code) { |
| // TODO(loyso): Record |code| for this specific case in |
| // Webapp.SyncInitiatedFullInstallResult UMA. |
| if (IsSuccess(code)) { |
| DCHECK_EQ(app_in_sync_install_id, installed_app_id); |
| std::move(callback).Run(installed_app_id, code); |
| } else { |
| // Install failed. Do the fallback install to generate icons. |
| finalizer()->FinalizeFallbackInstallAfterSync(app_in_sync_install_id, |
| std::move(callback)); |
| } |
| } |
| |
| void WebAppInstallManager::OnWebAppUninstalledAfterSync( |
| std::unique_ptr<WebApp> web_app, |
| OnceUninstallCallback callback, |
| bool uninstalled) { |
| UMA_HISTOGRAM_BOOLEAN("Webapp.SyncInitiatedUninstallResult", uninstalled); |
| std::move(callback).Run(web_app->app_id(), uninstalled); |
| // web_app data is destroyed here. |
| } |
| |
| content::WebContents* WebAppInstallManager::EnsureWebContentsCreated() { |
| if (web_contents_) |
| return web_contents_.get(); |
| |
| DCHECK(!web_contents_ready_); |
| |
| web_contents_ = WebAppInstallTask::CreateWebContents(profile()); |
| |
| // Load about:blank so that the process actually starts. |
| url_loader_->LoadUrl(GURL("about:blank"), web_contents_.get(), |
| WebAppUrlLoader::UrlComparison::kExact, |
| base::BindOnce(&WebAppInstallManager::OnWebContentsReady, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| return web_contents_.get(); |
| } |
| |
| void WebAppInstallManager::OnWebContentsReady(WebAppUrlLoader::Result result) { |
| DCHECK_EQ(WebAppUrlLoader::Result::kUrlLoaded, result); |
| web_contents_ready_ = true; |
| |
| MaybeStartQueuedTask(); |
| } |
| |
| } // namespace web_app |